Introduction
The release of React version 19 has been accompanied with a set of new features and enhancements.
However, upgrading to this new version requires certain modifications in the source code. This process can be quite taxing and repetitive, particularly for large codebases.
This article illustrates the usage of ast-grep, a tool designed to locate and substitute patterns in your codebase, towards easing your migration to React 19.
We will focus on three major codemods:
- Use
<Context>
as a provider - Remove implicit ref callback return
- Use ref as props and remove
forwardRef
Prerequisite: Setting up ast-grep
Initially, you need to set up ast-grep. This can be carried out via npm:
npm install -g @ast-grep/cli
Once installed, the proper set up of ast-grep can be confirmed by running the command below:
ast-grep --version
Use <Context>
as a provider
Let's kick off with the simplest modification: use as a provider.
React 19 uses <Context>
as a provider instead of <Context.Provider>
:
function App() {
const [theme, setTheme] = useState('light');
// ...
return (
- <UseTheme.Provider value={theme}>
+ <UseTheme value={theme}>
<Page />
- </UseTheme.Provider>
+ </UseTheme>
);
}
With ast-grep, the pattern $CONTEXT.Provider
can be found and replaced with $CONTEXT
.
However, the pattern $CONTEXT.Provider
could appear in multiple locations, requiring us to specifically look for it within a JSX opening and closing element.
We can utilize the ast-grep playground to find the correct names for JSX opening elements and JSX closing elements.
Afterwards, using inside
and any
, we can pinpoint that the pattern should exist within a JSX opening element and a JSX closing element.
id: use-context-as-provider
language: javascript
rule:
pattern: $CONTEXT.Provider
inside:
any:
- kind: jsx_opening_element
- kind: jsx_closing_element
fix: $CONTEXT
You can launch the rule from a YAML file using the command below:
ast-grep scan -r use-context-as-provider.yml
Remove implicit-ref-callback-return
Our next example is to remove implicit ref
's return.
React 19 now supports returning a cleanup function from ref callbacks.
Due to the introduction of ref cleanup functions, returning anything else from a ref callback will now be rejected by TypeScript. The fix is usually to stop using implicit returns, for example:
- <div ref={current => (instance = current)} />
+ <div ref={current => {instance = current}} />
How can we find this pattern? We should find the pattern <div ref={$A => $B}/>
where $B
is not a statement block.
First, ast-grep can use pattern object to find jsx_attribute
, which is the AST kind for the attribute in a JSX element.
You can use ast-grep's playground to find out the kind name.
Next, we need to find the pattern <div ref={$A => $B}/>
. The pattern means that we are looking for a JSX element with a ref
attribute that has a callback function.
pattern:
context: <div ref={$A => $B}/>
selector: jsx_attribute
Finally, we need to check if $B
is not a statement block. We can use the constraints
field to specify this condition.
constraints:
B:
not: {kind: statement_block}
Combining all these pieces, we get the following rule:
id: remove-implicit-ref-callback-return
language: javascript
rule:
pattern:
context: <div ref={$A => $B}/>
selector: jsx_attribute
constraints:
B:
not: {kind: statement_block}
fix: $A => {$B}
Remove forwardRef
Let's explore our most complex change: Remove forwardRef. In our example, we will only focus on the simplest case where no arrow function nor TypeScript is involved.
The example code is as follows:
const MyInput = forwardRef(function MyInput(props, ref) {
return <input {...props} ref={ref} />;
});
Let's first start simple. We can find the pattern forwardRef($FUNC)
.
However, the $FUNC
does not capture the arguments of the function. We need to capture the arguments of the function and rewrite them in the fix.
This pattern rule captures the function arguments.
rule:
pattern: forwardRef(function $M($PROPS, $REF) { $$$BODY })
Next, we need to rewrite the arguments of the function. The easiest way is to rewrite $PROPS
as an object destructuring and to add ref into the object destructuring.
rule:
pattern: forwardRef(function $M($PROPS, $REF) { $$$BODY })
fix: |-
function $M({ref: $REF, ...$PROPS}) {
$$$BODY
}
Handle more complex cases
The above rule handles single identifier arguments gracefully. But it does not work for more complex cases like object destructuring, which is popular in React components.
const MyInput = forwardRef(function MyInput({value}, ref) {
return <input value={value} ref={ref} />;
});
We can use rewriters
to handle more complex cases. The basic idea is that we can break down the rewriting into two scenarios: object destructuring and identifiers.
The object
rewriter captures the object destructuring pattern and extract the inner content.
id: object
rule:
pattern:
context: ({ $$$ARGS }) => {}
selector: object_pattern
fix: $$$ARGS
For example, the rewriter above will capture the {value}
inside function MyInput({value}, ref)
and extract value
as $$$ARGS
.
The identifier
rewriter captures the identifier pattern and spreads it.
id: identifier
rule: { pattern: $P }
fix: ...$P
For example, the rewriter above will capture props
and spread it as ...props
.
Finally, we can use the rewriters
field to register the two rules above.
rewriters:
- id: object
rule:
pattern:
context: ({ $$$ARGS }) => {}
selector: object_pattern
fix: $$$ARGS
- id: identifier
rule: { pattern: $P }
fix: ...$P
And we then can use them in the transform
field to rewrite the arguments and use them in the fix.
transform:
NEW_ARG:
rewrite:
rewriters: [object, identifier]
source: $PROPS
fix: |-
function $M({ref: $REF, $NEW_ARG}) {
$$$BODY
}
Putting all these together, we get the final rule:
id: remove-forward-ref
language: javascript
rule:
pattern: forwardRef(function $M($PROPS, $REF) { $$$BODY })
rewriters:
- id: object
rule:
pattern:
context: ({ $$$ARGS }) => {}
selector: object_pattern
fix: $$$ARGS
- id: identifier
rule: { pattern: $P }
fix: ...$P
transform:
NEW_ARG:
rewrite:
rewriters: [object, identifier]
source: $PROPS
fix: |-
function $M({ref: $REF, $NEW_ARG}) {
$$$BODY
}
Conclusion
Codemod is a powerful paradigm to automate code changes. In this article, we have shown how to use ast-grep to migrate to React 19.
We have covered three common changes: use as a provider, remove implicit-ref-callback-return, and remove forwardRef.
The examples here are for educational purposes and you are encouraged to use codemod.com to automate these changes in your codebase. codemod.com has curated rules that take care of these changes and more subtle edge cases.
You can adapt the example and explore ast-grep's power to automate more code changes.
ast-grep is also available on codemod.com.
Happy coding!
Top comments (0)