Introduction

React Compiler is a new build-time tool that automatically optimizes your React app. It works with plain JavaScript, and understands the Rules of React, so you don’t need to rewrite any code to use it.

You will learn

  • What React Compiler does
  • Getting started with the compiler
  • Incremental adoption strategies
  • Debugging and troubleshooting when things go wrong
  • Using the compiler on your React library

What does React Compiler do?

React Compiler automatically optimizes your React application at build time. React is often fast enough without optimization, but sometimes you need to manually memoize components and values to keep your app responsive. This manual memoization is tedious, easy to get wrong, and adds extra code to maintain. React Compiler does this optimization automatically for you, freeing you from this mental burden so you can focus on building features.

Before React Compiler

Without the compiler, you need to manually memoize components and values to optimize re-renders:

import { useMemo, useCallback, memo } from 'react';

const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
const processedData = useMemo(() => {
return expensiveProcessing(data);
}, [data]);

const handleClick = useCallback((item) => {
onClick(item.id);
}, [onClick]);

return (
<div>
{processedData.map(item => (
<Item key={item.id} onClick={() => handleClick(item)} />
))}
</div>
);
});

Note

This manual memoization has a subtle bug that breaks memoization:

<Item key={item.id} onClick={() => handleClick(item)} />

Even though handleClick is wrapped in useCallback, the arrow function () => handleClick(item) creates a new function every time the component renders. This means that Item will always receive a new onClick prop, breaking memoization.

React Compiler is able to optimize this correctly with or without the arrow function, ensuring that Item only re-renders when props.onClick changes.

After React Compiler

With React Compiler, you write the same code without manual memoization:

function ExpensiveComponent({ data, onClick }) {
const processedData = expensiveProcessing(data);

const handleClick = (item) => {
onClick(item.id);
};

return (
<div>
{processedData.map(item => (
<Item key={item.id} onClick={() => handleClick(item)} />
))}
</div>
);
}

See this example in the React Compiler Playground

React Compiler automatically applies the optimal memoization, ensuring your app only re-renders when necessary.

Deep Dive

What kind of memoization does React Compiler add?

React Compiler’s automatic memoization is primarily focused on improving update performance (re-rendering existing components), so it focuses on these two use cases:

  1. Skipping cascading re-rendering of components
    • Re-rendering <Parent /> causes many components in its component tree to re-render, even though only <Parent /> has changed
  2. Skipping expensive calculations from outside of React
    • For example, calling expensivelyProcessAReallyLargeArrayOfObjects() inside of your component or hook that needs that data

Optimizing Re-renders

React lets you express your UI as a function of their current state (more concretely: their props, state, and context). In its current implementation, when a component’s state changes, React will re-render that component and all of its children — unless you have applied some form of manual memoization with useMemo(), useCallback(), or React.memo(). For example, in the following example, <MessageButton> will re-render whenever <FriendList>’s state changes:

function FriendList({ friends }) {
const onlineCount = useFriendOnlineCount();
if (