Skip to content

Commit 19b9bc8

Browse files
Support forwardRef typed via TypeScript type arguments (Generics) (#134)
* ✨ Add support for forwardRefs typed via Generics * docs(changeset): Add support for forwardRefs typed via TS type arguments (aka generics)
1 parent 8576137 commit 19b9bc8

File tree

4 files changed

+154
-12
lines changed

4 files changed

+154
-12
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'extract-react-types': minor
3+
---
4+
5+
Add support for forwardRefs typed via TS type arguments (aka generics)

packages/extract-react-types/__snapshots__/converters-typescript.test.js.snap

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,68 @@ Object {
7474
}
7575
`;
7676

77+
exports[`forwardRef typed via function args 1`] = `
78+
Object {
79+
"component": Object {
80+
"kind": "generic",
81+
"name": Object {
82+
"kind": "id",
83+
"name": "MyComponent",
84+
"type": null,
85+
},
86+
"value": Object {
87+
"kind": "object",
88+
"members": Array [
89+
Object {
90+
"key": Object {
91+
"kind": "id",
92+
"name": "foo",
93+
},
94+
"kind": "property",
95+
"optional": false,
96+
"value": Object {
97+
"kind": "string",
98+
},
99+
},
100+
],
101+
"referenceIdName": "MyComponentProps",
102+
},
103+
},
104+
"kind": "program",
105+
}
106+
`;
107+
108+
exports[`forwardRef typed via generic types 1`] = `
109+
Object {
110+
"component": Object {
111+
"kind": "generic",
112+
"name": Object {
113+
"kind": "id",
114+
"name": "MyComponent",
115+
"type": null,
116+
},
117+
"value": Object {
118+
"kind": "object",
119+
"members": Array [
120+
Object {
121+
"key": Object {
122+
"kind": "id",
123+
"name": "foo",
124+
},
125+
"kind": "property",
126+
"optional": false,
127+
"value": Object {
128+
"kind": "string",
129+
},
130+
},
131+
],
132+
"referenceIdName": "MyComponentProps",
133+
},
134+
},
135+
"kind": "program",
136+
}
137+
`;
138+
77139
exports[`ts any 1`] = `
78140
Object {
79141
"component": Object {

packages/extract-react-types/converters-typescript.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,40 @@ const TESTS = [
485485
486486
export default MyComponent;
487487
`
488+
},
489+
{
490+
name: 'forwardRef typed via generic types',
491+
typeSystem: 'typescript',
492+
code: `
493+
import React, { forwardRef } from 'react';
494+
495+
type MyComponentProps = {
496+
foo: string,
497+
}
498+
499+
const MyComponent = forwardRef<HTMLElement, MyComponentProps>((props, ref) => {
500+
return <span>Foo</span>;
501+
});
502+
503+
export default MyComponent;
504+
`
505+
},
506+
{
507+
name: 'forwardRef typed via function args',
508+
typeSystem: 'typescript',
509+
code: `
510+
import React, { forwardRef } from 'react';
511+
512+
type MyComponentProps = {
513+
foo: string,
514+
}
515+
516+
const MyComponent = forwardRef((props: MyComponentProps, ref: HTMLElement) => {
517+
return <span>Foo</span>;
518+
});
519+
520+
export default MyComponent;
521+
`
488522
}
489523
];
490524

packages/extract-react-types/src/index.js

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,9 @@ function convertReactComponentClass(path, context) {
9797
return addDefaultProps(classProperties, defaultProps);
9898
}
9999

100-
function convertReactComponentFunction(path, context) {
100+
function convertReactComponentFunction(path, context, propTypes) {
101101
// we have a function, assume the props are the first parameter
102-
let propType = path.get('params.0.typeAnnotation');
103-
let functionProperties = convert(propType, {
102+
let functionProperties = convert(propTypes, {
104103
...context,
105104
mode: 'type'
106105
});
@@ -1589,7 +1588,11 @@ function exportedComponents(programPath, componentsToFind: 'all' | 'default', co
15891588
path.isArrowFunctionExpression() ||
15901589
path.isFunctionDeclaration()
15911590
) {
1592-
let component = convertReactComponentFunction(path, context);
1591+
let component = convertReactComponentFunction(
1592+
path,
1593+
context,
1594+
path.get('params.0.typeAnnotation')
1595+
);
15931596
components.push({ name, path, component });
15941597
return;
15951598
}
@@ -1598,20 +1601,58 @@ function exportedComponents(programPath, componentsToFind: 'all' | 'default', co
15981601
components.push({ name, path, component });
15991602
return;
16001603
}
1601-
let isMemo = isSpecialReactComponentType(path, 'memo');
1602-
if (isMemo || isSpecialReactComponentType(path, 'forwardRef')) {
1603-
let firstArg = path.get('arguments')[0];
1604+
1605+
const isMemo = isSpecialReactComponentType(path, 'memo');
1606+
const isForwardRef = isSpecialReactComponentType(path, 'forwardRef');
1607+
1608+
if (isMemo || isForwardRef) {
1609+
// Props typed via generics
1610+
const genericTypeParams = path.get('typeParameters');
1611+
1612+
// Props are the second type arg
1613+
if (isForwardRef && genericTypeParams && genericTypeParams.node) {
1614+
const component = convertReactComponentFunction(
1615+
genericTypeParams,
1616+
context,
1617+
genericTypeParams.get('params.1')
1618+
);
1619+
components.push({
1620+
name,
1621+
path,
1622+
component
1623+
});
1624+
return;
1625+
}
1626+
1627+
// Props typed via function arguments
1628+
const firstArg = path.get('arguments')[0];
16041629
if (firstArg) {
16051630
if (firstArg.isFunctionExpression() || firstArg.isArrowFunctionExpression()) {
1606-
let component = convertReactComponentFunction(firstArg, context);
1607-
components.push({ name, path, component });
1631+
const component = convertReactComponentFunction(
1632+
firstArg,
1633+
context,
1634+
firstArg.get('params.0.typeAnnotation')
1635+
);
1636+
components.push({
1637+
name,
1638+
path,
1639+
component
1640+
});
16081641
return;
16091642
}
16101643
if (isMemo && isSpecialReactComponentType(firstArg, 'forwardRef')) {
1611-
let innerFirstArg = firstArg.get('arguments')[0];
1644+
const innerFirstArg = firstArg.get('arguments')[0];
16121645
if (innerFirstArg.isFunctionExpression() || innerFirstArg.isArrowFunctionExpression()) {
1613-
let component = convertReactComponentFunction(innerFirstArg, context);
1614-
components.push({ name, path, component });
1646+
const component = convertReactComponentFunction(
1647+
innerFirstArg,
1648+
innerFirstArg,
1649+
innerFirstArg.get('params.0.typeAnnotation')
1650+
);
1651+
components.push({
1652+
name,
1653+
path,
1654+
component
1655+
});
16151656
}
16161657
}
16171658
}

0 commit comments

Comments
 (0)