Introducing the Compiler in React 19

OpenReplay Tech Blog - Sep 4 - - Dev Community

by Mary Ovigueraye

React unnecessarily re-renders many components because it is purely runtime-based, unlike other frameworks like [Vue](https://vuejs.org/guide/introduction.html), [Svelte](https://svelte.dev/docs), and [Solid](https://www.solidjs.com/docs/latest). This article will explain how the React compiler solves this by handling the automatic memoization of code for efficiency, reducing unnecessary re-rendering and boosting application performance.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data.

OpenReplay

Happy debugging! Try using OpenReplay today.


Let's see how the React compiler works, how it’s different from traditional memoization, and how to use it in your projects. When the project is built, the compiler creates code that memoizes the components. When the app re-renders, the code generated by that compiler checks to see parts of the component without state changes and then returns the memoized code. This ensures precision when it updates with no additional effort on the developer's part and well-written code.

To properly understand how the compiler works, let's examine the current Babel transpiler. Run the code below in the REPL to see the transpiler's output.

export default function Hello() {
return(
  <div className="foo">Hello World </div>
 );
}
Enter fullscreen mode Exit fullscreen mode

You should see this output below. The transpiler converts the JSX in the function to React's jsx runtime function. The JSX is converted into jsx("div", { className: "foo", children: "Hello World" }). With this setup, a new React element is created every time the Hello() function is rendered. There’s no built-in memoization, which leads to unnecessary re-renders that adversely affect the app’s performance.

import { jsx as _jsx } from "react/jsx-runtime";

export default function Hello() {
  return /*#__PURE__*/_jsx("div", {
    className: "foo",
    children: "Hello World "
 });
}
Enter fullscreen mode Exit fullscreen mode

When you compare this to the React Compiler’s output for the same code in its REPL here, you get this output.

function Hello() {
  const $ = _c(1);
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = <div className="foo">Hello World </div>;
    $[0] = t0;
 } else {
    t0 = $[0];
 }
  return t0;
}
Enter fullscreen mode Exit fullscreen mode

Here, it introduces a caching mechanism Symbol.for("react.memo_cache_sentinel")) to know when to use a cached version of the function. The $ variable is an array used to store cached values. The compiler would check if a cache exists before it re-renders. So, it only re-renders parts of the function when it is necessary.

Comparison with Existing Memoization Techniques

React Compiler simplifies memoization, but let’s compare it to the approaches that existed earlier, such as React.memo, useMemo, and useCallback.

Feature React.memo useMemo useCallback React Compiler
Automatic vs. Manual Manual Manual Manual Automatic
Granularity Component Level Value Level Function Level Integrated into rendering
Ease of Use Requires explicit use Requires explicit use Requires explicit use Built-in, less developer effort
Dependency Management Managed by developer Managed by developer Managed by developer Managed by compiler
Performance Impact Significant if used correctly Significant if used correctly Significant if used correctly Consistent performance gains
Potential for Bugs Higher due to manual control Higher due to manual control Higher due to manual control Lower due to automatic control
Example Syntax React.memo(MyComponent) useMemo(() => fn, [deps]) useCallback(() => fn, [deps]) None. The compiler handles it

Some benefits of automatic memoization are:

  • Simplicity: It would make codes simpler and elegant as developers would not need to manually apply memoization using React.memo, useMemo, or useCallback hooks
  • Performance: It would improve app performance by reducing unnecessary re-renders. This would be most useful for complex apps where manual memoization can be challenging.

It is important to note that when the compiler sees code that isn’t well-written in React, it defaults to the original transpiler.

Demonstrating React App Before Compilation

In this section, we will demonstrate how the React app behaves before the compiler optimizes it. Observing the difference during run time allows us to spot some inefficiencies, such as unnecessary rendering. It would make us appreciate the improvement made by the React compiler. Let's examine the code below:

function CustomHeader() {
  console.log("CustomHeader is re-rendering");
  return (
    <header>
      <h1>Custom Counter</h1>
    </header>
 );
}

function CustomCounter() {
  const [count, setCount] = useState(0);
  return (
    <>
      <CustomHeader />
      <div>
        <p>{count}</p>
        <button onClick={() => setCount(count + 1)}>Increment</button>
      </div>
    </>
 );
}

export default CustomCounter;
Enter fullscreen mode Exit fullscreen mode

This is a React app in which the transpiler re-renders the whole component, even with components that don’t have state changes. The CustomHeader function in this code doesn’t change, but it re-renders. We see this through the console whenever we click the increment button; it logs "CustomHeader is re-rendering" when the component re-renders.

Image of the console showing the CustomHeader function continuously re-rendering without any state changes.

We could fix this by using useMemo to memoize the code. It would watch the code for state changes and only render the component when there is a state change. Below are the changes we would make to the code.

function CustomHeader() {
  console.log("CustomHeader is re-rendering");
  return (
    <header>
      <h1>Custom Counter</h1>
    </header>
 );
}

function CustomCounter() {
  const [count, setCount] = useState(0);
    // Memoize the JSX for the header
    const headerJSX = useMemo(() => <CustomHeader />, []);
  return (
    <>
      {headerJSX}
      <div>
        <p>{count}</p>
        <button onClick={() => setCount(count + 1)}>Increment</button>
      </div>
    </>
 );
}

export default CustomCounter;
Enter fullscreen mode Exit fullscreen mode

In this code, we wrap the CustomHeader in a useMemo hook. When we look at the console, this component only renders once, even after clicking the increment button multiple times.

A preview of the console showing the CustomHeader component renders once and doesn’t re-render even when the increment button is clicked multiple times.

Demonstration with the React Compiler

When we run our initial code in the compiler, it doesn’t re-render the CustomHeader component. Instead, it automatically memoizes the code and renders it once, just as it did when we used the useMemo() hook.

You can check this yourself by following these steps to install the React compiler.

  • First, install Vite by running the code below. Then, select the React option and Javascript.
 npm create vite@latest .
Enter fullscreen mode Exit fullscreen mode
  • Install React 19.
 npm i react@rc react-dom@rc 
Enter fullscreen mode Exit fullscreen mode

You can confirm the version in the package.json file.

  • Next, install the React compiler.
 npm add babel-plugin-react-compiler
Enter fullscreen mode Exit fullscreen mode
  • Open the vite.config.js file and add these configurations for the React compiler to enable it to work.
const ReactCompilerConfig = { /* ... */ };

export default defineConfig(() => {
  return {
    plugins: [
      react({
        babel: {
          plugins: [
 ["babel-plugin-react-compiler", ReactCompilerConfig],
 ],
 },
 }),
 ],
 };
});
Enter fullscreen mode Exit fullscreen mode

The compiler is set up. Next, we paste our initial code into the App.jsx file and run it. If it is set up correctly, the CustomHeader only renders once, as seen in the console. This shows that the compiler automatically memoizes the code without the useMemo() hook we used for the traditional method.

The image shows the console only rendering the CustomHeader function once, even when we continually click on the increment button because of the compiler.

Can you run into errors while using the compiler?

You can encounter errors when using the React compiler. These errors can result from three main issues: errors arising from violating React's rules, infinite loops during runtime, and build-time errors.

Using the eslint-plugin-react-compiler will display any violations of the rules of React in your editor. If a component violates this rule, the compiler can skip it and try to optimize the following components.

Let's examine a code example that violates a rule in React and observe the output in the REPL.

import React, { useState, useEffect } from 'react';

function useConditionalHook(shouldUseEffect) {
  const [count, setCount] = useState(0);

  if (shouldUseEffect) {
    useEffect(() => {
      console.log('Effect is running');
      // This effect will only run if shouldUseEffect is true
 }, []);
 }

  return [count, setCount];
}

function App() {
  const [count, setCount] = useConditionalHook(true);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
 );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In the code above, the hook is called conditionally, which goes against the rule in React. Hooks should not be called conditionally; they should be called in a consistent order. This error is fired off by the eslint-plugin-react-compiler, as observed in the REPL output below.

The image below shows the error in the React compiler playground when the React cod

However it is important to note that without the eslint-plugin-react-compiler, the code above would run in React 19 uses concurrent mode as the default rendering mode. In this mode, React can safely handle conditional hook calls. It is still essential to follow the rules of React even though React 19 is forgiving.

Conclusion

React 19's introduction of a compiler has helped address the inefficiencies of the previous Babel transpiler. This handles unnecessary re-rendering by introducing a cache system, making the code simpler, and removing errors introduced by manual implementation. This generally improves the app’s performance and efficiency.

Additional Resources

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player