Article Summaries | A (Mostly) Complete Guide to React Rendering Behavior

October 27th, 2023

Article: A (Mostly) Complete Guide to React Rendering Behavior

This article is truly excellent, and I would highly advise that you go and read the original in its entirety. I like to create these summaries to further my understanding of the learnings from the original article, so while this summary will hopefully be somewhat helpful for you, it is also largely to cement the learnings that I took when reading the article.

I am also going to mainly focus on the advanced (although starting with the basics) learnings about rendering in React as discussed by the article, as well as how Context affects rendering in React. There is an additional section about react-redux in the original article that I will skip for the purposes of this summary. But again, please go read the original article, it’s really well done.

TL;DR: Context and Rendering Behavior in React

  • Rendering is fundamental to React’s architecture, and is not inherently “bad”
  • Rendering in React occurs in a top-down manner. If component A is the parent of component B, a re-render of component A will by default cause a re-render of component B
  • Use React.memo() to prevent unnecessary re-renders
  • Be aware of context boundaries in multi-renderer apps

Basics of Rendering in React

In React, rendering is a fundamental process that determines how the user interface (UI) is updated based on the current application state.

  • Virtual DOM

    React uses a virtual representation of the actual DOM, known as the Virtual DOM. When there’s a change in the application state, React creates a new Virtual DOM tree, which is a lightweight copy of the actual DOM. This allows React to efficiently update the real DOM by minimizing direct manipulation, which can be slow.

  • Reconciliation

    Reconciliation is the process of comparing the new Virtual DOM tree with the previous one to identify the differences (or “diffs”). React figures out what has changed in the component tree and updates the real DOM accordingly.

  • Component Rendering

    Components are the building blocks of a React application. When a component’s state or props change, it triggers a re-render of that component. React follows a top-down approach, starting from the root component and working its way down the component tree.

  • Functional Components

    Functional components are a simpler way to define components in React. With the introduction of React hooks, functional components can manage state and have lifecycle-like behavior. The component’s return value defines what it should render.

  • Reconciliation and Efficiency

    React aims to minimize unnecessary rendering. It compares new Virtual DOM elements with the previous ones to identify what changed. This approach optimizes performance and prevents excessive updates to the actual DOM.

  • Conditional Rendering

    Conditional rendering allows components to render different content based on certain conditions. By using conditional statements, you can control what the component displays.

  • Reconciliation Strategies

    React has built-in strategies for optimizing reconciliation, including the use of keys in lists. Keys help React identify which items have changed, been added, or been removed from a list, improving the efficiency of updates.

  • Memoization and Memoization Libraries

    Memoization is a technique for optimizing function or component calls by storing and reusing their results. React provides the React.memo() higher-order component to prevent re-renders when the component’s props have not changed.

React.memo and Props:

In React, the React.memo() higher-order component is a valuable tool for optimizing component rendering. It’s specifically designed to prevent unnecessary re-renders when a component’s props have not changed. This can significantly enhance the performance of your application, as it helps in avoiding rendering when there’s no actual change in the data that a component depends on.

  • Preventing Unnecessary Renders: The primary purpose of React.memo() is to make your functional components behave similarly to class components that extend PureComponent. When you wrap a component with React.memo(), it automatically checks the props passed to the component and determines whether the component should re-render. If the props haven’t changed since the last render, the component won’t re-render, saving valuable processing time.

  • Memoizing Functional Components: When you wrap a functional component with React.memo(), it memoizes the component, meaning it caches the result of the component rendering based on the props. If the component is rendered with the same props again, it returns the cached result instead of rendering the component again.

  • Usage: You can use React.memo() by simply wrapping your functional component declaration. For example:

    const MyComponent = React.memo((props) => {
      // Your component code here
    });
    
  • Shallow Comparison: React.memo() uses a shallow comparison of props to determine if they have changed. It compares the new props with the previous props by checking if the values of the props are different. If any of the prop values change, the component will re-render.

  • Avoid Overuse: While React.memo() can be a powerful tool, it’s essential to use it judiciously. Applying it to every component might not be necessary and can add unnecessary complexity to your code. It’s most beneficial when dealing with components that are potentially expensive to render, such as lists or complex UI elements.

  • Limitations: Keep in mind that React.memo() can only prevent re-renders due to prop changes. If a component relies on internal state changes or context updates, it will still re-render when those changes occur. To optimize rendering in these cases, you might need to combine React.memo() with other techniques, like state management libraries or context optimizations.

  • Custom Comparisons: In some cases, you might want to implement a custom comparison function instead of relying on the default shallow comparison. You can do this by providing a second argument to React.memo() that specifies your custom comparison function.

Context and Rendering Behavior:

In React, context plays a crucial role in sharing data between components and can have a significant impact on rendering behavior. Understanding how context works is essential for efficient component rendering.

  • Sharing Data with Context:

    • Context allows you to share data, like states or functions, between components without having to pass it explicitly through props. It’s particularly useful when data needs to be accessible to deeply nested components.
    • Context is created using the React.createContext() method, which provides a context object.
  • Context Providers and Consumers:

    • Context providers are components that supply the context data. They use the Provider component from the context object.
    • Context consumers are components that consume the context data. They use the Consumer component from the context object or the useContext hook.
  • Rendering Cascade:

    • When a context provider’s value changes, React triggers a rendering cascade. This means that not only the immediate children of the provider re-render but also any nested components that consume the context.
    • React does this because it can’t distinguish which nested components are interested in the context value, so it forces all of them to update.
  • Optimizing Context Updates:

    • It’s crucial to optimize context updates to prevent unnecessary renders.
    • Wrapping the child components of a context provider in React.memo() or using {props.children} can help avoid re-renders of the entire component tree when the context value updates.
    • This way, only components that explicitly consume the context value will re-render, not the entire subtree.
  • Context and Render Boundaries:

    • In a typical React app, context is limited to a single renderer, like ReactDOM or React Native. However, context providers don’t pass their value through renderer boundaries.
    • If you have multiple renderers in your app, context may not flow through them, affecting components in different rendering environments.
  • Context Updates and Render Optimizations:

    • To optimize context updates, you can use the React.memo() HOC to wrap components that should not re-render if their props don’t change.
    • The upcoming “React Forget” compiler aims to automatically memoize components, potentially eliminating unnecessary renders.