9 May 2024 · Software Engineering

    React Compiler: What Is It and How Will It Change Frontend Development?

    11 min read
    Contents

    React 18 has been around for more than two years, and it is finally time to welcome React 19. The main innovation introduced, the one we all love is the React Compiler! It promises to forever simplify frontend development by eliminating the need for manual memoization optimizations.

    In this guide, you will understand what React Compiler is, how it works, what benefits it introduces, and how to prepare your application.

    What React 19 Brings To the Table

    React 19, to be released at React Conf 2024 on May 15-16, 2024, is the long-awaited next version of this framework. React 18 was released in 2022, and since then, web technologies have evolved greatly. It is high time for an update.

    Not only will version 19 be a step forward but it promises to forever change the way developers build applications in React. Some of the most exciting features React 19 plans to introduce are:

    • Server components: Server-side rendering of components for faster page loading and better SEO. By processing components on the server before delivering the page to users, React 19 enables faster website loading times, improved search engine visibility, and smoother data management. Next.js already uses this feature.
    • Actions: Streamline the management of data and interactions within web pages. Actions make it easier to update page information through forms, removing complexities and simplifying the user experience.
    • Optimized asset loading: Background loading of site assets for smoother page changes. React 19 can begin loading images and other files in the background while users are still browsing the current page, reducing wait times during page transitions.
    • Document metadata: Streamlined SEO management thanks to the new <DocumentHead> component. Adding titles and meta tags to pages will be more intuitive, improving search engine optimization without the need for repetitive coding.
    • Web components: Improved compatibility with the Web Components standard for more flexible and compatible frontend development.
    • Enhanced hooks: Greater control over the lifecycle and state of components through both existing and new hooks. The end goal is to simplify the coding process, making React development more efficient and enjoyable.
    • React compiler: Automatic conversion of React code into standardized, optimized JavaScript code. It allows compiled React code to automatically render only the right parts of the UI when the state changes, reducing the need for useMemouseCallback, and memo. This means faster React applications with simplified codebases.

    All of these features are great, sure, but what stands out is React Compiler. This upgrade promises to change React development forever. Let’s now try to understand the why behind it and what benefits it will introduce!

    React Compiler: Everything You Need to Know

    Dig into the React 19 Compiler tool, exploring what it is and how it works.

    React’s Core Mental Model

    To understand the why behind React Compiler, you first need to delve into some key React concepts.

    At the heart of React is a declarative and component-based mental model. In frontend development, declarative programminginvolves describing the desired end state of the UI without specifying each step to get there through DOM manipulation. Meanwhile, the component-based approach breaks down UI elements into reusable, concise, self-contained building blocks, promoting modularization and ease of maintenance.

    To efficiently identify specific DOM elements that require updates, React employs an in-memory representation of the user interface called the virtual DOM. In case of changes to the application state, React compares the virtual DOM with the real DOM, identifies the minimum set of changes needed, and accurately updates the real DOM.

    In short, the mental model is that React re-renders whenever the state of the application changes. However, sometimes React can be too reactive, resulting in unnecessary re-renders that significantly slow down your application.

    Re-Rendering Hell: The Need for a Compiler

    React’s agility in responding to changes in application state is a double-edged sword. On the one hand, it simplifies frontend development due to its declarative approach. On the other, it can lead to excessive re-rendering of the components in the UI in response of state changes.

    Re-rendering problems are particularly common when dealing with JavaScript data structures such as objects and arrays. The problem is that there is no computationally efficient way to compare two objects and arrays to see if they are equivalent (have the same keys and values) in JavaScript.

    Consider the following scenario. You have a React component that generates a new object or array upon each render, as in the example below:

    import React from "react";
    
    const AlphabetList = () => {
      // define the alphabet array
      const alphabet = Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i));
    
      return (
        <div>
          <h2>Alphabet List</h2>
          <ul>
            {/* render the alphabet as list items */}
            {alphabet.map((letter, index) => (
              <li key={index}>{letter}</li>
            ))}
          </ul>
        </div>
      );
    };
    
    export default AlphabetList;

    Although the content of the local array will be the same at each rendering, React cannot efficiently know that. As a result, it may trigger a re-render in the nested DOM elements of the component relying on the values in that array, unaware that the UI should remain identical. This re-rendering mechanism can quickly spiral out of control, significantly impacting application performance and user experience.

    To optimize re-rendering behavior and avoid those issues, React developers must manually introduce memoization into their components. In React, memoization involves caching the results of expensive computations or component outputs based on their input parameters. By storing and reusing these results, memoization helps prevent unnecessary re-reading of components, improving the overall efficiency and performance of a React application.

    React 18 provides several memoization tools:

    • React.memo(): A higher-order function to avoid re-rendering a component when its props remain unchanged.
    • useMemo(): A React hook that caches the result of a calculation between re-renders, reducing redundant computations.
    • useCallback(): A hook from React that caches the definition of a function between re-renders, avoiding unnecessary recreations of functions. Learn more in our guide to the React useCallback() hook.

    Thanks to the useMemo() hook, you can optimize the <AlphabetList> component to avoid unnecessary re-renders as follows:

    import React, { useMemo } from "react";
    
    const AlphabetList = () => {
      // define the alphabet array via useMemo()
      const alphabet = useMemo(() => {
        return Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i));
        // no dependencies, so it will only be calculated once on the first render
      }, []);
    
      return (
        <div>
          <h2>Alphabet List</h2>
          <ul>
            {/* render the alphabet as list items */}
            {alphabet.map((letter, index) => (
              <li key={index}>{letter}</li>
            ))}
          </ul>
        </div>
      );
    };
    
    export default AlphabetList;

    The memoization tools offered by React are certainly powerful. At the same time, their introduction is nothing more than a clear departure from the declarative philosophy underlying React’s core mental model. This is because the burden falls on developers, who must not only describe the final state of the UI but also explicitly manage rendering optimizations. Manual memoization also introduces code complexity and maintenance headaches.

    The solution? An advanced compiler capable of converting React code into optimized JavaScript code, so that components are automatically re-rendered only in case of significant changes in state values.

    What Is React Compiler?

    React Compiler, also known as React Forget, is an optimizing compiler for React. It now powers Instagram’s web portal in production and will be deployed on other Meta applications before its first open-source release.

    The original goal of the compiler was to enforce React’s core programming model by automatically generating the equivalent of memouseMemo, and useCallback calls to minimize the cost of re-rendering. The project has evolved greatly from its beginnings, moving from an “auto-memoizing compiler” to an “automatic reactivity compiler.”

    The real objective of React Forget is now to ensure that React applications have the right amount of reactivity by default. In other words, apps should re-render only when state values meaningfully change. Currently, React re-renders a component when object identity changes. With React Forget, it will re-render only when the semantic value of an object changes — but without incurring the runtime cost of deep comparisons.

    From an implementation point of view, React Compiler applies automatic memoization. However, the team behind it considers reactivity framing as a more complete way to understand what it does. If you want to learn more about the inner workings and logic of the React compiler, read the React Labs section dedicated to it.

    See React Forget in action in the video below: https://www.youtube.com/watch?v=qOQClO3g8-Y&

    While JavaScript is a notoriously difficult language to optimize because of its dynamic nature and loose rules, React Compiler can compile code safely by modeling both JavaScript rules and the “rules of React.” These rules limit what developers can do, helping to carve out a safe space for the compiler to perform its optimizations.

    The Rules of React

    React comes with a set of rules that are intended to support high-quality web applications. Developers should follow these rules, which also represent what React Compiler is based on.

    Some of the most important rules of React are:

    • Components must be idempotent: React components should always produce the same output given the same set of inputs, which include props, state, and context.
    • Side effects must run outside of render: Side effects, such as data fetching, subscriptions, or manually changing the DOM, should not be performed during the rendering process of a component. Instead, they should be executed in lifecycle hooks like useEffect.
    • Props and state are immutable: Props and state in React components should be treated as immutable, meaning they should not be directly modified. Changing props or state directly can lead to bugs and unpredictable behavior.
    • Return values and arguments to hooks are immutable: Once values are passed to a React hook, they should not be modified. Hooks rely on the immutability of their arguments and return values to maintain consistency and predictability in component behavior.
    • Values are immutable after being passed to JSX: Do not mutate values used in JSX after this has been rendered. Any necessary mutations should be performed before the JSX is created to ensure that the rendered output remains consistent.
    • Never call component functions directly: React components should only be used within JSX and not called directly as regular functions.
    • Never pass around hooks as regular values: React hooks, such as useState and useEffect, should only be called inside functional components. Using them as regular values can lead to unexpected behavior and violate the rules of hooks.
    • Only call hooks at the top level: React hooks should always be called at the top level of functional components, before any conditional statements or loops. This ensures that hooks are called in the same order on every render and maintain their intended behavior.
    • Only call hooks from React functions: Hooks should only be called from within React function components or custom hooks. Calling hooks from regular JavaScript functions can lead to errors and violate the rules of hooks.

    Enable Strict Mode and configure React’s ESLint plugin to make sure that your React application follows these rules.

    Benefits and Hopes

    The main benefits introduced by React Compiler are:

    • No more memoization headaches: Developers no longer have to manually implement and manage memoization strategies in their code. This reduces complexity and the likelihood of errors, streamlining the development process.
    • Better developer experience: Developers can focus more on building features and less on performance optimization, leading to increased productivity and satisfaction. They will finally be able to fully embrace the React declarative approach.
    • Faster React applications: Render components only when necessary, minimizing unnecessary computations and overhead. This results in faster and more responsive user interfaces, improving overall performance and user experience.

    These are all promising changes, but we have yet to see what effect this new tool will have on code development. To ensure that the compiler does its job, your code must follow the rules of React. Here is why the official team recommends adopting ESLint and similar tools to prepare your application for React Compiler.

    React Compiler: A Frontend Revolution?

    It is difficult to say soon whether the React Compiler will be enough to spark a real revolution in the realm of frontend development. What is for sure is that it has all the credentials to significantly change the development of future React applications. By introducing automatic memoization, this compiler can automatically speed up React applications and improve the developer experience. These are just some of the benefits this promising ambitious project brings to the tabl.

    React Conf 2024 is just around the corner, and we look forward to seeing what breakthroughs this ambitious project will have on the frontend world!

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    I'm a software engineer, but I prefer to call myself a Technology Bishop. Spreading knowledge through writing is my mission.
    Avatar
    Reviewed by:
    I picked up most of my skills during the years I worked at IBM. Was a DBA, developer, and cloud engineer for a time. After that, I went into freelancing, where I found the passion for writing. Now, I'm a full-time writer at Semaphore.