Skip to content

Commit

Permalink
Add hoisting support for ref components
Browse files Browse the repository at this point in the history
Summary:
This adds hoisting logic to components with refs. This is needed since the `ref` transform adds a `const ... = React.forwardRef(...);`. This inserted variable declaration has different hoisting semantics vs a function declaration of a component. So this change finds any references and hoists the statement above the first reference. e.g.
```
Bar;
unrelated;

Bar;
component Bar(foo: string, ref: Ref) {}
```
Transforms into:
```
const Bar = React.forwardRef(Bar_withRef);
Bar;
unrelated;

Bar;
function Bar_withRef({
  foo
}: $ReadOnly<{...}>, ref: Ref): React.Node {}"
```

The way this works is when a component statement is found with a `ref` param we walk from the first statement to the component traversing the full tree, if a component reference is found we abort walking and return the position. Then inject the forwardRef statement before that position.

Reviewed By: mvitousek, jbrown215

Differential Revision: D48247491

fbshipit-source-id: 77714765f04bba0cb369389e364b0981a9075419
  • Loading branch information
pieterv authored and facebook-github-bot committed Aug 24, 2023
1 parent 96222cb commit 201aade
Show file tree
Hide file tree
Showing 3 changed files with 855 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -326,4 +326,71 @@ switch (thing) {
`);
});
});

describe('ref and normal params with hoisting', () => {
const code = `
Bar;
unrelated;
someSideEffect(Foo);
unrelated;
component Foo(foo: string, ref: Ref) {}
Bar;
component Bar(foo: string, ref: Ref) {}
`;

test('ESTree', async () => {
expect(await printForSnapshotESTree(code)).toBe(code.trim());
expect(await parseForSnapshotESTree(code)).toMatchSnapshot();
});

test('Babel', async () => {
expect(await parseForSnapshotBabel(code)).toMatchSnapshot();
expect(await printForSnapshotBabel(code)).toMatchInlineSnapshot(`
"const Bar = React.forwardRef(Bar_withRef);
Bar;
unrelated;
const Foo = React.forwardRef(Foo_withRef);
someSideEffect(Foo);
unrelated;
function Foo_withRef({
foo
}: $ReadOnly<{...}>, ref: Ref): React.Node {}
Bar;
function Bar_withRef({
foo
}: $ReadOnly<{...}>, ref: Ref): React.Node {}"
`);
});
});

describe('ref and normal params with hoisting (recursive)', () => {
const code = `
component Foo(bar: mixed = Foo, ref: any) {
return null;
}
`;

test('ESTree', async () => {
expect(await printForSnapshotESTree(code)).toBe(code.trim());
expect(await parseForSnapshotESTree(code)).toMatchSnapshot();
});

test('Babel', async () => {
expect(await parseForSnapshotBabel(code)).toMatchSnapshot();
expect(await printForSnapshotBabel(code)).toMatchInlineSnapshot(`
"const Foo = React.forwardRef(Foo_withRef);
function Foo_withRef({
bar = Foo
}: $ReadOnly<{...}>, ref: any): React.Node {
return null;
}"
`);
});
});
});
Loading

0 comments on commit 201aade

Please sign in to comment.