I tried React Compiler today, and guess what... 😉
Tags: react, webdev, javascript, performance
Published: 2024-06-10 08:18:00


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


This is probably the most clickbaity title I’ve come up with, but I feel like an article about one of the most hyped topics in the React community these days deserves it 😅.

For the last two and a half years, after I release any piece of content that mentions patterns related to re-renders and memoization, visitors from the future would descend into the comments section and kindly inform me that all I just said is not relevant anymore because of React Forget (currently known as React Compiler).

Now that our timeline has finally caught up with theirs and React Compiler is actually released to the general public as an experimental feature, it’s time to investigate whether those visitors from the future are correct and see for ourselves whether we can forget about memoization in React starting now.

## What is React Compiler


But first, very, very briefly, what is this compiler, what problem does it solve, and how do you get started with it?

**The problem**: Re-renders in React are cascading. Every time you change state in a React component, you trigger a re-render of that component, every component inside, components inside of those components, etc., until the end of the component tree is reached.

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


If those downstream re-renders affect some heavy components or happen too often, this might cause performance problems for our apps.

One way to fix those performance problems is to prevent that chain of re-renders from happening, and one way to do that is with the help of memoization: `React.memo`, `useMemo`, and `useCallback`. Typically, we’d wrap a component in `React.memo`, all of its props in `useMemo` and `useCallback`, and next time, when the parent component re-renders, the component wrapped in `memo` (i.e., “memoized”) won’t re-render.

But using those tools correctly is hard, _**very**_ hard. I’ve written a few articles and done a few videos on this topic if you want to test your knowledge of it ([How to useMemo and useCallback: you can remove most of them](https://www.developerway.com/posts/how-to-use-memo-use-callback), [Mastering memoization in React - Advanced React course, Episode 5](https://youtu.be/huBxeruVnAM)).

This is where React Compiler comes in. The compiler is a tool developed by the React core team. It plugs into our build system, grabs the original components' code, and tries to convert it into code where components, their props, and hooks' dependencies are memoized by default. The end result is similar to wrapping everything in `memo`, `useMemo,` or `useCallback`.

This is just an approximation to start wrapping our heads around it, of course. In reality, it does much more complicated transformations. Jack Herrington did a good overview of that in his recent video ([React Compiler: In-Depth Beyond React Conf 2024](https://www.youtube.com/watch?v=PYHBHK37xlE)), if you want to know the actual details. Or, if you want to break your brain completely and truly appreciate the complexity of this, watch the [“React Compiler Deep Dive”](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=9309s&ab_channel=ReactConf) talk where Sathya Gunasekaran explains the Compiler and Mofei Zhang then live-codes it in 20 minutes 🤯.

If you want to try out the Compiler yourself, just follow the docs: [https://react.dev/learn/react-compiler](https://react.dev/learn/react-compiler). They are good enough already and have all the requirements and how-to steps. Just remember: this is still a very experimental thing that relies on installing the canary version of React, so be careful.

That’s enough of the preparation. Let’s finally look at what it can do and how it performs in real life.

## Trying out the Compiler

For me, the main purpose of this article was to investigate whether our expectations of the Compiler match reality. What is the current promise?

- The Compiler is plug-and-play: you install it, and it Just Works; there is no need to rewrite existing code.
- We will never think about `React.memo`, `useMemo,` and `useCallback` again after it’s installed: there won’t be any need.

To test those assumptions, I implemented a few simple examples to test the Compiler in isolation and then ran it on three different apps I have available.

### Simple examples: testing the Compiler in isolation


The full code of all the simple examples is available here: [https://github.com/developerway/react-compiler-test](https://github.com/developerway/react-compiler-test)

The easiest way to start with the Compiler from scratch is to install the canary version of Next.js. Basically, this will give you everything you need:

```bash
npm install next@canary babel-plugin-react-compiler
```

Then we can turn the Compiler on in the `next.config.js`:

```js
const nextConfig = {
  experimental: {
    reactCompiler: true,
  },
};

module.exports = nextConfig;
```

And voila! We’ll immediately see auto-magically memoized components in React Dev Tools.

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


The assumption one is correct so far: installing it is pretty simple, and it Just Works.

Let’s start writing code and see how the Compiler deals with it.

#### First example: simple state change.

```tsx
const SimpleCase1 = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>
        toggle dialog
      </button>
      {isOpen && <Dialog />}
      <VerySlowComponent />
    </div>
  );
};
```

We have an `isOpen` state variable that controls whether a modal dialog is open or not, and a `VerySlowComponent` rendered in the same component. Normal React behavior would be to re-render `VerySlowComponent` every time the `isOpen` state changes, leading to the dialog popping up with a delay.

Typically, if we want to solve this situation with memoization (although there are other ways, of course), we’d wrap `VerySlowComponent` in `React.memo`:

```tsx
const VerySlowComponentMemo = React.memo(VerySlowComponent);

const SimpleCase1 = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      ...
      <VerySlowComponentMemo />
    </>
  );
};
```

With the Compiler, it’s pure magic: we can ditch the `React.memo`, and still see in the dev tools that the `VerySlowComponent` is memoized, the delay is gone, and if we place `console.log` inside the `VerySlowComponent`, we’ll see that indeed, it’s not re-rendered on state change anymore.

The full code of these examples is [available here.](https://github.com/developerway/react-compiler-test/blob/main/src/components/simple-cases.tsx)

#### Second example: props on the slow component.

So far so good, but the previous example is the simplest one. Let’s make it a bit more complicated and introduce props into the equation.

Let’s say our `VerySlowComponent` has an `onSubmit` prop that expects a function and a `data` prop that accepts an array:

```tsx
const SimpleCase2 = () => {
  const [isOpen, setIsOpen] = useState(false);

  const onSubmit = () => {};
  const data = [{ id: 'bla' }];

  return (
    <>
      ...
      <VerySlowComponent onSubmit={onSubmit} data={data} />
    </>
  );
};
```

Now, in the case of manual memoization, on top of wrapping `VerySlowComponent` in `React.memo`, we’d need to wrap the array in `useMemo` (let’s assume we can’t just move it outside for some reason) and `onSubmit` in `useCallback`:

```tsx
const VerySlowComponentMemo = React.memo(VerySlowComponent);

export const SimpleCase2Memo = () => {
  const [isOpen, setIsOpen] = useState(false);

  // memoization here
  const onSubmit = useCallback(() => {}, []);

  // memoization here
  const data = useMemo(() => [{ id: 'bla' }], []);

  return (
    <div>
      ...
      <VerySlowComponentMemo
        onSubmit={onSubmit}
        data={data}
      />
    </div>
  );
};
```

But with the Compiler, we still don’t need to do that! `VerySlowComponent` still appears as memoized in React dev tools, and the “control” console.log inside it is still not fired.

You can run these examples locally [from this repo](https://github.com/developerway/react-compiler-test/blob/main/src/components/simple-cases.tsx).

#### Third example: elements as children.

Okay, the third example, before testing a real app. What about the case where almost no one can memoize correctly? What if our slow component accepts children?

```tsx
export const SimpleCase3 = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      ...
      <VerySlowComponent>
        <SomeOtherComponent />
      </VerySlowComponent>
    </>
  );
};
```

Can you, off the top of your head, remember how to memoize `VerySlowComponent` correctly here?

Most people would assume that we’d need to wrap both `VerySlowComponent` and `SomeOtherComponent` in `React.memo`. This is incorrect. We'd need to wrap our `<SomeOtherComponent />` element into `useMemo` instead, like this:

```tsx
const VerySlowComponentMemo = React.memo(VerySlowComponent);

export const SimpleCase3 = () => {
  const [isOpen, setIsOpen] = useState(false);

  // memoize children via useMemo, not React.memo
  const child = useMemo(() => <SomeOtherComponent />, []);

  return (
    <>
      ...
      <VerySlowComponentMemo>{child}</VerySlowComponentMemo>
    </>
  );
};
```

If you’re unsure why this is the case, you can watch this video that explains memoization in detail, including this pattern: [Mastering memoization in React - Advanced React course, Episode 5](https://youtu.be/huBxeruVnAM). This article can also be useful: [The mystery of React Element, children, parents and re-renders](https://www.developerway.com/posts/react-elements-children-parents)

Luckily, the React Compiler still works its magic ✨ here! Everything is memoized, the very slow component doesn’t re-render.

Three hits out of three so far, that’s impressive! But those examples are very simple. When’s life that easy in reality? Let’s try a real challenge now.

### Testing the Compiler on real code


To really challenge the Compiler, I ran it on three codebases I have available:

- **App One**: A few years old and quite large app, based on React, React Router & Webpack, written by multiple people.
- **App Two**: Slightly newer but still quite large React & Next.js app, written by multiple people.
- **App Three**: My personal project: very new, latest Nextjs, very small - a few screens with CRUD operations.

For every app, I did:

- [initial health check](https://react.dev/learn/react-compiler#checking-compatibility) to determine the readiness of the app for the Compiler.
- enabled Compiler’s eslint rules and ran them on the entire codebase.
- updated React version to 19 canary.
- installed the Compiler.
- identified some visible cases of unnecessary re-renders before turning on the Compiler.
- turned on the Compiler and checked whether those unnecessary re-renders were fixed.

### Testing the Compiler on App One: results


This one is the biggest, probably around 150k lines of code for the React part of the app. I identified **10** easy-to-spot cases of unnecessary re-renders for this app. Some were pretty minor, like re-rendering a whole header component when clicking a button inside. Some were bigger, like re-rendering the entire page when typing in an input field.

- **Initial health check:** 97.7% of the components could be compiled! No incompatible libraries.
- **Eslint check**: just 20 rule violations
- **React 19 update**: a few minor things broke, but after commenting them out, the app seemed to be working fine.
- **Installing the Compiler**: this one produced a few F-bombs and required some help from ChatGPT since it’s been a while since I last touched anything Webpack or Babel-related. But in the end, it also worked.
- **Testing the app**: out of 10 cases of unnecessary re-renders … only 2 were fixed by the Compiler 😢

2 out of 10 was a pretty disappointing result. But this app had some eslint violations that I haven’t fixed, maybe that’s why? Let’s take a look at the next app.

### Testing the Compiler on App Two: results


This app is much smaller, something like 30k lines of React code. Here I also identified **10** unnecessary re-renders.

- **Initial health check:** Same result, 97.7% components could be compiled.
- **Eslint check**: just 1 rule violation! 🎉Perfect candidate.
- **React 19 update** & **installing the Compiler**: for this, I had to update Next.js to the canary version, it took care of the rest. It just worked after the installation, was much easier than updating the Webpack-based app.
- **Testing the app**: out of 10 cases of unnecessary re-renders… only 2 again were fixed by the compiler 😢

2 out of 10 again! On a perfect candidate… Again, a bit disappointing. That’s real life against synthetic “counter” examples for you. Let’s take a look at the third app before trying to debug what’s going on.

### Testing the Compiler on App Three: results


This is the smallest of them all, written in a weekend or two. Just a few pages with a table of data, and the ability to add/edit/remove an entity in the table. The entire app is so small and so simple that I was able to identify only 8 unnecessary re-renders in it. Everything re-renders on every interaction there, I haven’t optimized it in any way.

Perfect subject for the React Compiler to drastically improve the re-renders situation!

- **Initial health check:** 100% of components can be compiled
- **Eslint check**: no violations 🎉
- **React 19 update** & **installing the Compiler**: surprisingly worse than the previous one. Some of the libraries that I used were not compatible with React 19 yet. I had to force-install the dependencies to silence the warnings. But the actual app and all the libraries still worked, so no harm, I guess.
- **Testing the app**: out of 8 cases of unnecessary re-renders, the React Compiler managed to fix… drum roll… one. **Only one**! 🫠 At this point, I almost started crying; I had such hopes for this test.

This is something that my old clinical nature expected, but definitely not something that my naive inner child was hoping for. Maybe I’m just writing React code wrong? Can I investigate what went wrong with memoization by the Compiler, and can it be fixed?

## Investigating the results of memoization by the Compiler

To debug these issues in a useful manner, I extracted one of the pages from the third app into its own repo. You can check it out here: ([https://github.com/developerway/react-compiler-test/](https://github.com/developerway/react-compiler-test/) ) if you want to follow my train of thought and do a code-along exercise. It’s almost exactly one of the pages I have in the third app, just with fake data and a few things removed (like SSR) to simplify the debugging experience.

The UI is very simple: a table with a list of countries, a “delete” button for each row, and an input component under the table where you can add a new country to the list.

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


From the code perspective, it’s just one component with one state, queries, and mutations. Here’s the [full code](https://github.com/developerway/react-compiler-test/blob/main/src/components/countries-broken.tsx). The simplified version with only the necessary information for the investigation looks like this:

```tsx
export const Countries = () => {
  // store what we type in the input here
  const [value, setValue] = useState("");

  // get the full list of countries with react-query
  const { data: countries } = useQuery(...);

  // mutation to delete a country with react-query
  const deleteCountryMutation = useMutation(...);

  // mutation to add a country with react-query
  const addCountryMutation = useMutation(...);

  // callback that is passed to the "delete" button
  const onDelete = (name: string) => deleteCountryMutation.mutate(name);

  // callback that is passed to the "add" button
  const onAddCountry = () => {
    addCountryMutation.mutate(value);
    setValue("");
  };

  return (
    ...
      {countries?.map(({ name }, index) => (
        <TableRow key={`${name.toLowerCase()}`}>
          ...
          <TableCell className="text-right">
            <!-- onDelete is here -->
            <Button onClick={() => onDelete(name)} variant="outline">
              Delete
            </Button>
          </TableCell>
        </TableRow>
      ))}
    ...
    <Input
      type="text"
      placeholder="Add new country"
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
    <button onClick={onAddCountry}>Add</button>
  );
};
```

Since it’s just one component with multiple states (local + query/mutation updates), everything re-renders on every interaction. If you start the app, you’ll have these cases of unnecessary re-renders:

- typing into the “Add new country” input causes everything to re-render.
- clicking “delete” causes everything to re-render.
- clicking “add” causes everything to re-render.

For a simple component like this, I’d expect the Compiler to fix all of this. Especially considering that in the React Dev Tools, everything seems to be memoized:

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

However, try enabling the “Highlight updates when components render” setting and enjoy the light show.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5z4kag9o9p0ufq5gs9nm.gif)


Adding `console.log` to every component inside the table gives us the exact list: everything except for the header components still re-renders on every state update from all sources.

How to investigate why, though? 🤔

React Dev Tools doesn’t give any additional information. I _could_ copy-paste that component into the [Compiler Playground](https://playground.react.dev/#N4Igzg9grgTgxgUxALhAMygOzgFwJYSYAEAYjHgpgCYAyeYOAFMEWuZVWEQL4CURwADrEicQgyKEANnkwIAwtEw4iAXiJQwCMhWoB5TDLmKsTXgG5hRInjRFGbXZwB0UygHMcACzWr1ABn4hEWsYBBxYYgAeADkIHQ4uAHoAPksRbisiMIiYYkYs6yiqPAA3FMLrIiiwAAcAQ0wU4GlZBSUcbklDNqikusaKkKrgR0TnAFt62sYHdmp+VRT7SqrqhOo6Bnl6mCoiAGsEAE9VUfmqZzwqLrHqM7ubolTVol5eTOGigFkEMDB6u4EAAhKA4HCEZ5DNZ9ErlLIWYTcEDcIA) and see what happens… But take a look at the output! 😬 That feels like a step in the wrong direction, and to be frank, the last thing I want to do, ever.

The only thing that comes to mind is to incrementally memoize that table and see whether something fishy is going on with components or dependencies.

## Investigating via manual memoization


This part is for those who fully understand how all manual memoization techniques work. If you’re feeling uneasy about `React.memo`, `useMemo,` or `useCallback`, I recommend watching [this video](https://youtu.be/huBxeruVnAM) first.

Also, I’d recommend opening the code locally ([https://github.com/developerway/react-compiler-test](https://github.com/developerway/react-compiler-test) ) and doing a code-along exercise; it would make following the train of thought below much easier.

#### Investigating typing into input re-renders

Let’s look at that table again, this time in full:

```tsx
<Table>
  <TableCaption>Supported countries list.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead className="w-[400px]">Name</TableHead>
      <TableHead className="text-right">Action</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {countries?.map(({ name }, index) => (
      <TableRow key={`${name.toLowerCase()}`}>
        <TableCell className="font-medium">
          <Link href={`/country/${name.toLowerCase()}`}>
            {name}
          </Link>
        </TableCell>
        <TableCell className="text-right">
          <Button
            onClick={() => onDelete(name)}
            variant="outline"
          >
            Delete
          </Button>
        </TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>
```

The fact that header components were memoized hints to us what the Compiler did: it probably wrapped all components in a `React.memo` equivalent, and the part inside `TableBody` is memoized with a `useMemo` equivalent. And the `useMemo` equivalent has something in its dependencies that is updated with every re-render, which in turn causes everything inside `TableBody` to re-render, including `TableBody` itself. At least it’s a good working theory to test.

If I replicate the memoization of that content part, it might give us some clues:

```tsx
// memoize the entire content of TableBody
const body = useMemo(
  () =>
    countries?.map(({ name }, index) => (
      <TableRow key={`${name.toLowerCase()}`}>
        <TableCell className="font-medium">
          <Link href={`/country/${name.toLowerCase()}`}>
            {name}
          </Link>
        </TableCell>
        <TableCell className="text-right">
          <Button
            onClick={() => onDelete(name)}
            variant="outline"
          >
            Delete
          </Button>
        </TableCell>
      </TableRow>
    )),
  // these are the dependencies used in that bunch of code
  // thank you eslint!
  [countries, onDelete],
);
```

Now it’s clearly visible that this entire part depends on the `countries` array of data and the `onDelete` callback. The `countries` array is coming from a query, so it can’t possibly be re-created on every re-render - caching this is one of the primary responsibilities of the library.

The `onDelete` callback looks like this:

```tsx
const onDelete = (name: string) => {
  deleteCountryMutation.mutate(name);
};
```

In order for it to go into the dependencies, it should be memoized as well:

```tsx
const onDelete = useCallback(
  (name: string) => {
    deleteCountryMutation.mutate(name);
  },
  [deleteCountryMutation],
);
```

And `deleteCountryMutation` is a mutation from react-query again, so it’s likely okay:

```tsx
const deleteCountryMutation = useMutation({...});
```

The final step is to memoize the `TableBody` and render the memoized child. If everything is memoized correctly, then re-rendering of rows and cells when typing in the input should stop.

```tsx
const TableBodyMemo = React.memo(TableBody);

// render that inside Countries
<TableBodyMemo>{body}</TableBodyMemo>;
```

Aaaand, it didn’t work 🤦🏻‍♀️ Now we’re getting somewhere - I messed something up with the dependencies, and the Compiler probably did the same. But what? Aside from `countries`, I only have one dependency - `deleteCountryMutation`. I made an assumption that it’s safe, but is it really? What’s actually inside? Luckily, [the source code is available](https://github.com/TanStack/query/blob/main/packages/react-query/src/useMutation.ts#L15). `useMutation` is a hook that does a bunch of things and returns this:

```tsx
const mutate = React.useCallback(...)

return { ...result, mutate, mutateAsync: result.mutate }
```

It’s a non-memoized object in the return!! I was wrong in my assumption that I could just use it as a dependency.

`mutate` itself is memoized, however. So in theory, I just need to pass it to the dependencies instead:

```tsx
// extract mutate from the returned object
const { mutate: deleteCountry } = useMutation(...);

// pass it as a dependency instead
const onDelete = useCallback(
  (name: string) => {
    // use it here directly
    deleteCountry(name);
  },
  // hello, memoized dependency
  [deleteCountry],
);
```

After this step, finally, our manual memoization works.

Now, in theory, if I just remove all that manual memoization and leave the `mutate` fix in place, the React Compiler should be able to pick it up.

And indeed, it does! Table rows and cells don’t re-render anymore when I type something 🎉

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


However, re-renders on “add” and “delete” a country are still present. Let’s fix those as well.

#### Investigating “add” and “delete” re-renders

Let’s take a look at the `TableBody` code again.

```tsx
<TableBody>
  {countries?.map(({ name }, index) => (
    <TableRow key={index}>
      <TableCell className="font-medium">
        <Link href={`/country/${name.toLowerCase()}`}>
          {name}
        </Link>
      </TableCell>
      <TableCell className="text-right">
        <Button
          onClick={() => onDelete(name)}
          variant="outline"
        >
          Delete
        </Button>
      </TableCell>
    </TableRow>
  ))}
</TableBody>
```

This entire thing re-renders when I add or remove a country from the list. Let’s apply the same strategy again: what would I've done here if I wanted to memoize those components manually?

It’s a dynamic list, so I’d have to:

**First**, make sure that the “key” property matches the country, not the position in the array. `index` won’t do - if I remove a country from the beginning of the list, the index will change for every row below, which will force a re-render regardless of memoization. In real life, I’d have to introduce some sort of `id` for each country. For our simplified case, let’s just use `name` and make sure we’re not adding duplicate names - keys should be unique.

```tsx
{
  countries?.map(({ name }) => (
    <TableRow key={name}>...</TableRow>
  ));
}
```

**Second**, wrap `TableRow` in `React.memo`. Easy.

```tsx
const TableRowMemo = React.memo(TableRow);
```

**Third**, memoize the `children` of `TableRow` with `useMemo`:

```tsx
{
  countries?.map(({ name }) => (
    <TableRow key={name}>
      ... // everything inside here needs to be memoized
      with useMemo
    </TableRow>
  ));
}
```

which is impossible since we’re inside render and inside an array: hooks can only be used at the top of the component outside of the render function.

To pull this off, we need to extract the entire `TableRow` with its content into a component:

```tsx
const CountryRow = ({ name, onDelete }) => {
  return (
    <TableRow>
      <TableCell className="font-medium">
        <Link href={`/country/${name.toLowerCase()}`}>
          {name}
        </Link>
      </TableCell>
      <TableCell className="text-right">
        <Button
          onClick={() => onDelete(name)}
          variant="outline"
        >
          Delete
        </Button>
      </TableCell>
    </TableRow>
  );
};
```

pass data through props:

```tsx
<TableBody>
  {countries?.map(({ name }) => (
    <CountryRow
      name={name}
      onDelete={onDelete}
      key={name}
    />
  ))}
</TableBody>
```

and wrap `CountryRow` in `React.memo` instead. `onDelete` is memoized correctly - we already fixed it.

I didn’t even need to implement that manual memoization. As soon as I extracted those rows into a component, the Compiler immediately picked them up, and re-renders stopped 🎉. 2 : 0 in the human-against-the-machine battle.

Interestingly enough, the Compiler is able to pick up everything inside the `CountryRow` component but not the component itself. If I remove manual memoization but keep the `key` and `CountryRow` change, cells and rows will stop re-rendering on add/delete, but the `CountryRow` component itself still re-renders.

At this point, I’m out of ideas on how to fix it with the Compiler, and it’s enough material for the article already, so I’ll just let it re-render. Everything inside is memoized, so it's not that huge of a deal.

## So, what’s the verdict?


The Compiler performs amazingly on simple cases and simple components. Three hits out of three! However, real life is a bit more complicated.

In all three apps that I tried the Compiler on, it was able to fix only 1-2 cases of noticeable unnecessary re-renders out of 8-10 that I spotted.

However, with a bit of deductive thinking and guesswork, it looks like it’s possible to improve that result with minor code changes. Investigating those, however, is very non-trivial, requires a lot of creative thinking, and mastery of React algorithms and existing memoization techniques.

The changes I had to make in the existing code in order for the Compiler to behave:

- extract `mutate` from the return value of the `useMutation` hook and use it in the code directly.
- extract `TableRow` and everything inside into an isolated component.
- change the “key” from `index` to `name`.

You can check out the code [before](https://github.com/developerway/react-compiler-test/blob/main/src/components/countries-broken.tsx) and [after](https://github.com/developerway/react-compiler-test/blob/main/src/components/countries-fixed.tsx) and play with the app yourself.

As for the assumptions that I was investigating:

**Does it “just work”?** Technically, yep. You can just turn it on, and nothing seems to be broken. It won’t memoize everything correctly, though, despite showing it as memoized in React Dev Tools.

**Can we forget about** `memo`, `useMemo,` and `useCallback` after installing the Compiler? Absolutely not! At least not in its current state. In fact, you’ll need to know them even better than it’s needed now and develop a sixth sense for writing components optimized for the Compiler. Or just use them to debug the re-renders you want to fix.

That’s assuming we want to fix them, of course. I suspect what will happen is this: we'll all just turn on the Compiler when it’s production-ready. Seeing all those “memo ✨” in Dev Tools will give us a sense of security, so everyone will just relax about re-renders and focus on writing features. The fact that half of the re-renders are still there no one will notice, since most of the re-renders have a negligible effect on performance anyway.

And for cases where re-renders actually have a performance impact, it will be easier to fix them with composition techniques like [moving state down](https://www.developerway.com/posts/react-re-renders-guide#part3.2), [passing elements as children](https://www.developerway.com/posts/react-re-renders-guide#part3.3) or props, or extracting data into [Context with splitted providers](https://www.developerway.com/posts/how-to-write-performant-react-apps-with-context) or any external state management tool that allows memoized selectors. And once in a blue moon - manual `React.memo` and `useCallback`.

As for those visitors from the future, I’m pretty sure now that they are from a parallel universe. A marvelous place where React just happens to be written in something more structured than the notoriously flexible JavaScript, and the Compiler actually can solve 100% of the cases because of it.


---

Originally published at [https://www.developerway.com](https://www.developerway.com). The website has more articles like this 😉

Take a look at the [Advanced React book](https://advanced-react.com/) to take your React knowledge to the next level.

[Subscribe to the newsletter](https://www.developerway.com), [connect on LinkedIn](https://www.linkedin.com/in/adevnadia/) or [follow on Twitter](https://twitter.com/adevnadia) to get notified as soon as the next article comes out.