Migrate to React 19 with ast-grep
Published: 2024-04-29 04:00:02

## Introduction
The release of React version 19 has been accompanied with a set of new features and enhancements.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tleer15ab4j3tfovyrsv.png)

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](https://ast-grep.github.io/), a tool designed [to locate and substitute patterns](https://github.com/ast-grep/ast-grep) 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:

```bash
npm install -g @ast-grep/cli
```

Once installed, the proper set up of ast-grep can be confirmed by running the command below:

```bash
ast-grep --version
```

## Use `<Context>` as a provider

Let's kick off with the simplest modification: use <Context> as a provider.

React 19 uses `<Context>` as a provider instead of `<Context.Provider>`:

```diff
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](https://ast-grep.github.io/playground.html) to find the correct names for JSX opening elements and JSX closing elements.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/747vdz2xcyrozsoigflh.png)


Afterwards, using [`inside`](https://ast-grep.github.io/guide/rule-config/relational-rule.html) and [`any`](https://ast-grep.github.io/guide/rule-config/composite-rule.html#any), we can pinpoint that the pattern should exist within a JSX opening element and a JSX closing element.

```yaml
id: use-context-as-provider
language: javascript
rule:
  pattern: $CONTEXT.Provider
  inside:
    any:
    - kind: jsx_opening_element
    - kind: jsx_closing_element
fix: $CONTEXT
```

[Example playground link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIkJCRLRUVQIiwiY29uZmlnIjoicnVsZTpcbiAgcGF0dGVybjogJFRIRU1FLlByb3ZpZGVyXG4gIGluc2lkZTpcbiAgICBhbnk6XG4gICAgLSBraW5kOiBqc3hfb3BlbmluZ19lbGVtZW50XG4gICAgLSBraW5kOiBqc3hfY2xvc2luZ19lbGVtZW50XG5maXg6ICRUSEVNRVxuIiwic291cmNlIjoiZnVuY3Rpb24gQXBwKCkge1xuICBjb25zdCBbdGhlbWUsIHNldFRoZW1lXSA9IHVzZVN0YXRlKCdsaWdodCcpO1xuICAvLyAuLi5cbiAgcmV0dXJuIChcbiAgICA8VXNlVGhlbWUuUHJvdmlkZXIgdmFsdWU9e3RoZW1lfT5cbiAgICAgIDxQYWdlIC8+XG4gICAgPC9Vc2VUaGVtZS5Qcm92aWRlcj5cbiAgKTtcbn1cbiJ9).


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01x0947mqi8pm54q663x.png)



You can [launch the rule from a YAML file](https://ast-grep.github.io/reference/cli/scan.html#r-rule-rule-file) using the command below:

```bash
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:

```diff
- <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](https://ast-grep.github.io/guide/rule-config/atomic-rule.html#pattern) 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.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3ow2qgb9kb0vlrt9lr9.png)


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.

```yaml
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`](https://ast-grep.github.io/guide/project/lint-rule.html#constraints) field to specify this condition.

```yaml
constraints:
  B:
    not: {kind: statement_block}
```

Combining all these pieces, we get the following rule:

```yaml
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}
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyexp5tydwohwpsnvq2y.png)


[Example playground link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIkJCRLRUVQIiwiY29uZmlnIjoicnVsZTpcbiAgcGF0dGVybjpcbiAgICBjb250ZXh0OiA8ZGl2IHJlZj17JEEgPT4gJEJ9Lz5cbiAgICBzZWxlY3RvcjoganN4X2F0dHJpYnV0ZVxuY29uc3RyYWludHM6XG4gIEI6XG4gICAgbm90OiB7a2luZDogc3RhdGVtZW50X2Jsb2NrfVxuZml4OiAkQSA9PiB7JEJ9Iiwic291cmNlIjoiZnVuY3Rpb24gTXlEaXYoKSB7XG4gIGNvbnN0IHJlZiA9IHVzZVJlZihudWxsKVxuICByZXR1cm4gPGRpdiByZWY9e2N1cnJlbnQgPT4gKHJlZi5jdXJyZW50ID0gY3VycmVudCl9IC8+XG59XG4ifQ==)


## 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:

```jsx
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.

```yaml
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.

```yaml
rule:
  pattern: forwardRef(function $M($PROPS, $REF) { $$$BODY })
fix: |-
  function $M({ref: $REF, ...$PROPS}) {
    $$$BODY
  }
```

[Example playground link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIkJCRLRUVQIiwiY29uZmlnIjoicnVsZTpcbiAgcGF0dGVybjogZm9yd2FyZFJlZihmdW5jdGlvbiAkTSgkUFJPUFMsICRSRUYpIHsgJCQkQk9EWSB9KVxuZml4OiB8LVxuICBmdW5jdGlvbiAkTSh7cmVmOiAkUkVGLCAuLi4kUFJPUFN9KSB7XG4gICAgJCQkQk9EWVxuICB9XG4iLCJzb3VyY2UiOiJjb25zdCBNeUlucHV0ID0gZm9yd2FyZFJlZihmdW5jdGlvbiBNeUlucHV0KHByb3BzLCByZWYpIHtcbiAgcmV0dXJuIDxpbnB1dCB7Li4ucHJvcHN9IHJlZj17cmVmfSAvPjtcbn0pO1xuIn0=)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqf4ukw9l07riv359mjn.png)


### 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.

```jsx
const MyInput = forwardRef(function MyInput({value}, ref) {
  return <input value={value} ref={ref} />;
});
```

We can use [`rewriters`](https://ast-grep.github.io/advanced/find-n-patch.html) 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.

```yaml
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.
```yaml
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.

```yaml
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.

```yaml
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:

```yaml
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
  }
```

[Example playground link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiRUSEVNRS5Qcm92aWRlciIsInJld3JpdGUiOiJsb2dnZXIubG9nKCRNQVRDSCkiLCJjb25maWciOiJydWxlOlxuICBwYXR0ZXJuOiBmb3J3YXJkUmVmKGZ1bmN0aW9uICRNKCRQUk9QUywgJFJFRikgeyAkJCRCT0RZIH0pXG5yZXdyaXRlcnM6XG4tIGlkOiBvYmplY3RcbiAgcnVsZTpcbiAgICBwYXR0ZXJuOlxuICAgICAgY29udGV4dDogKHsgJCQkQVJHUyB9KSA9PiB7fVxuICAgICAgc2VsZWN0b3I6IG9iamVjdF9wYXR0ZXJuXG4gIGZpeDogJCQkQVJHU1xuLSBpZDogaWRlbnRpZmllclxuICBydWxlOiB7IHBhdHRlcm46ICRQIH1cbiAgZml4OiAuLi4kUCBcbnRyYW5zZm9ybTpcbiAgTkVXX0FSRzpcbiAgICByZXdyaXRlOlxuICAgICAgcmV3cml0ZXJzOiBbb2JqZWN0LCBpZGVudGlmaWVyXVxuICAgICAgc291cmNlOiAkUFJPUFNcbmZpeDogfC1cbiAgZnVuY3Rpb24gJE0oe3JlZjogJFJFRiwgJE5FV19BUkd9KSB7IFxuICAgICQkJEJPRFlcbiAgfVxuIiwic291cmNlIjoiY29uc3QgTXlJbnB1dCA9IGZvcndhcmRSZWYoZnVuY3Rpb24gTXlJbnB1dChwcm9wcywgcmVmKSB7XG4gIHJldHVybiA8aW5wdXQgey4uLnByb3BzfSByZWY9e3JlZn0gLz47XG59KTtcblxuY29uc3QgTXlJbnB1dCA9IGZvcndhcmRSZWYoZnVuY3Rpb24gTXlJbnB1dCh7dmFsdWV9LCByZWYpIHtcbiAgcmV0dXJuIDxpbnB1dCB2YWx1ZT17dmFsdWV9IHJlZj17cmVmfSAvPjtcbn0pOyJ9)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3mjkn20qclgl04zuvrpf.png)



## 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 <Context> 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](https://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](https://docs.codemod.com/introduction). 

Happy coding!