Details on how React rendering behaves, and how use of Context and React-Redux affect rendering

I've seen a lot of ongoing confusion over when, why, and how React will re-render components, and how use of Context and React-Redux will affect the timing and scope of those re-renders. After having typed up variations of this explanation dozens of times, it seems it's worth trying to write up a consolidated explanation that I can refer people to. Note that all this information is available online already, and has been explained in numerous other excellent blog posts and articles, several of which I'm linking at the end in the "Further Information" section for reference. But, people seem to be struggling to put the pieces together for a full understanding, so hopefully this will help clarify things for someone.

What is "Rendering"?

Rendering is the process of React asking your components to describe what they want their section of the UI to look like, now, based on the current combination of props and state.

Rendering Process Overview

During the rendering process, React will start at the root of the component tree and loop downwards to find all components that have been flagged as needing updates. For each flagged component, React will call either classComponentInstance.render() (for class components) or FunctionComponent() (for function components), and save the render output.

A component's render output is normally written in JSX syntax, which is then converted to React.createElement() calls as the JS is compiled and prepared for deployment. createElement returns React elements, which are plain JS objects that describe the intended structure of the UI. Example:

After it has collected the render output from the entire component tree, React will diff the new tree of objects (frequently referred to as the "virtual DOM"), and collects a list of all the changes that need to be applied to make the real DOM look like the current desired output. The diffing and calculation process is known as "reconciliation".

React then applies all the calculated changes to the DOM in one synchronous sequence.

Render and Commit Phases

The React team divides this work into two phases, conceptually:

The "Render phase" contains all the work of rendering components and calculating changes

The "Commit phase" is the process of applying those changes to the DOM

After React has updated the DOM in the commit phase, it then synchronously runs the componentDidMount and componentDidUpdate class lifecycle methods, and the useLayoutEffect hooks.

React then sets a short timeout, and when it expires, runs all the useEffect hooks.

In React's upcoming "Concurrent Mode", it is able to pause the work in the rendering phase to allow the browser to process events. React will either resume, throw away, or recalculate that work later as appropriate. Once the render pass has been completed, React will still run the commit phase synchronously in one step.

A key part of this to understand is that "rendering" is not the same thing as "updating the DOM", and a component may be rendered without any visible changes happening as a result. When React renders a component:

The component might return the same render output as last time, so no changes are needed

In Concurrent Mode, React might end up rendering a component multiple times, but throw away the render output each time if other updates invalidate the current work being done

How Does React Handle Renders?

Queuing Renders

After the initial render has completed, there are a few different ways to tell React to queue a re-render:

Class components:

this.setState()

this.forceUpdate()

Function components:

useState setters

useReducer dispatches

Other:

Calling ReactDOM.render(<App>) again (which is equivalent to calling forceUpdate() on the root component)

Standard Render Behavior

It's very important to remember that:

React's default behavior is that when a parent component renders, React will recursively render all child components inside of it!

As an example, say we have a component tree of A > B > C > D, and we've already shown them on the page. The user clicks a button in B that increments a counter:

We call setState() in B, which queues a re-render of B.

React starts the render pass from the top of the tree

React sees that A is not marked as needing an update, and moves past it

React sees that B is marked as needing an update, and renders it. B returns <C /> as it did last time.

C was not originally marked as needing an update. However, because its parent B rendered, React now moves downwards and renders C as well. C returns <D /> again.

D was also not marked for rendering, but since its parent C rendered, React moves downwaard and renders D too.

To repeat this another way:

Rendering a component will, by default, cause all components inside of it to be rendered too!

Also, another key point:

In normal rendering, React does not care whether "props changed" - it will render child components unconditionally just because the parent rendered!

This means that calling setState() in your root <App> component, with no other changes altering the behavior, will cause React to re-render every single component in the component tree. After all, one of the original sales pitches for React was "act like we're redrawing the entire app on every update".

Now, it's very likely that most of the components in the tree will return the exact same render output as last time, and therefore React won't need to make any changes to the DOM. But, React will still have to do the work of asking components to render themselves and diffing the render output. Both of those take time and effort.

Remember, rendering is not a bad thing - it's how React knows whether it needs to actually make any changes to the DOM!

Improving Rendering Performance

Having said that, it's also true that that render work can be "wasted" effort at times. If a component's render output didn't change, and that part of the DOM didn't need to be updated, then the work of rendering that component was really kind of a waste of time.

React component render output should always be entirely based on current props and current component state. Therefore, if we know ahead of time that a component's props and state haven't changed, we should also know that the render output would be the same, that no changes are necessary for this component, and that we can safely skip the work of rendering it.

When trying to improve software performance in general, there are two basic approaches: 1) do the same work faster, and 2) do less work. Optimizing React rendering is primarily about doing less work by skipping rendering components when appropriate.

Component Render Optimization Techniques

React offers three primary APIs that allow us to potentially skip rendering a component:

React.Component.shouldComponentUpdate: an optional class component lifecycle method that will be called early in the render process. If it returns false, React will skip rendering the component. It may contain any logic you want to use to calculate that boolean result, but the most common approach is to check if the component's props and state have changed since last time, and return false if they're unchanged.

React.PureComponent: since that comparison of props and state is the most common way to implement shouldComponentUpdate, the PureComponent base class implements that behavior by default, and may be used instead of Component + shouldComponentUpdate.

React.memo(): a built-in "higher order component" type. It accepts your own component type as an argument, and returns a new wrapper component. The wrapper component's default behavior is to check to see if any of the props have changed, and if not, prevent a re-render. Both function components and class components can be wrapped using React.memo(). (A custom comparison callback may be passed in, but it really can only compare the old and new props anyway, so the main use case for a custom compare callback would be only comparing specific props fields instead of all of them.)

All of these approaches use a comparison technique called "shallow equality". This means checking every individual field in two different objects, and seeing if any of the contents of the objects are a different value. In other words, obj1.a === obj2.a && obj1.b === obj2.b && ......... This is typically a fast process, because === comparisons are very simple for the JS engine to do. So, these three approaches do the equivalent of const shouldRender = !shallowEqual(newProps, prevProps).

There's also a lesser-known technique as well: if a React component returns the exact same element reference in its render output as it did the last time, React will skip re-rendering that particular child.

For all of these techniques, skipping rendering a component means React will also skip rendering that entire subtree, because it's effectively putting a stop sign up to halt the default "render children recursively" behavior.

How New Props References Affect Render Optimizations

We've already seen that by default, React re-renders all nested components even if their props haven't changed. That also means that passing new references as props to a child component doesn't matter, because it will render whether or not you pass the same props. So, something like this is totally fine:

Every time ParentComponent renders, it will create a new onClick function reference and a new data object reference, then pass them as props to NormalChildComponent. (Note that it doesn't matter whether we're defining onClick using the function keyword or as an arrow function - it's a new function reference either way.)

That also means there's no point in trying to optimize renders for "host components", like a <div> or a <button>, by wrapping them up in a React.memo(). There's no child component underneath those basic components, so the rendering process would stop there anyway.

However, if the child component is trying to optimize renders by checking to see whether props have changed, then passing new references as props will cause the child to render. If the new prop references are actually new data, this is good. However, what if the parent component is just passing down a callback function?

Now, every time ParentComponent renders, these new references are going to cause MemoizedChildComponent to see that its props values have changed to new references, and it will go ahead and re-render... even though the onClick function and the data object should be basically the same thing every time!

This means that:

MemoizedChildComponent will always re-render even though we wanted to skip rendering most of the time

The work that it's doing to compare its old and new props is wasted effort

Similarly, note that rendering <MemoizedChild><OtherComponent /></MemoizedChild> will also force the child to always render, because props.children is always a new reference.

Optimizing Props References

Class components don't have to worry about accidentally creating new callback function references as much, because they can have instance methods that are always the same reference. However, they may need to generate unique callbacks for separate child list items, or capture a value in an anonymous function and pass that to a child. Those will result in new references, and so will creating new objects as child props while rendering. React doesn't have anything built-in to help optimize those cases.

For function components, React does provide two hooks to help you reuse the same references: useMemo for any kind of general data like creating objects or doing complex calculations, and useCallback specifically for creating callback functions.

Memoize Everything?

As mentioned above, you don't have throw useMemo and useCallback at every single function or object you pass down as a prop - only if it's going to make a difference in behavior for the child. (That said, the dependency array comparisons for useEffectdo add another use case where the child might want to receive consistent props references, which does make things more complicated.)

The other question that comes up all the time is "Why doesn't React wrap everything in React.memo() by default?".

Why doesn’t React put memo() around every component by default? Isn’t it faster? Should we make a benchmark to check?

Ask yourself:

Why don’t you put Lodash memoize() around every function? Wouldn’t that make all functions faster? Do we need a benchmark for this? Why not?

Also, while I don't have a specific link on it, it's possible that trying to apply this to all components by default might result in bugs due to cases where people are mutating data rather than updating it immutably.

I've had some public discussion with Dan about this on Twitter. I personally think it's likely that using React.memo() on a widespread basis would likely be a net gain in overall app rendering perf. As I said in an extended Twitter thread last year:

The React community as a whole seems to be over obsessed with "perf", yet much of the discussion revolves around outdated "tribal wisdom" passed down via Medium posts and Twitter comments rather than based on concrete usage.

There's definitely collective misunderstanding about the idea of a "render" and the perf impact. Yes, React is totally based around rendering - gotta render to do anything at all. No, most renders aren't overly expensive.

"Wasted" rerenders certainly aren't the end of the world. Neither is rerendering the whole app from the root.
That said, it's also true that a "wasted" rerender with no DOM update is CPU cycles that didn't need to be burned. Is that a problem for most apps? Probably not. Is it something that can be improved? Probably.

Are there apps where default "rerender it all" approaches aren't sufficient? Of course, that's why sCU, PureComponent, and memo() exist.

Should users wrap everything in memo() by default? Probably not, if only because you should think about your app's perf needs. Will it actually hurt if you do? No, and realistically I expect it does have a net benefit (despite Dan's points about wasted comparisons)

Are benchmarks flawed, and results highly variable based on scenarios and apps? Of course. That said, it would be REALLY REALLY HELPFUL if folks could start pointing at hard numbers for these discussions instead of playing the telephone game of "I saw a comment once..."

I'd love to see a bunch of benchmark suites from the React team and the larger community to measure a bunch of scenarios so we could stop arguing about most of this stuff once and for all. Function creation, render cost, optimization... CONCRETE EVIDENCE, PLEASE!

(And yes, this blog post is basically a long-delayed and much-expanded version of that tweet thread, although I'd actually forgotten I'd tweeted all that until I ran across it just now while researching the post.)

Measuring React Component Rendering Performance

Use the React DevTools Profiler to see what components are rendering in each commit. Find components that are rendering unexpectedly, use the DevTools to figure out why they rendered, and fix things (perhaps by wrapping them in React.memo(), or having the parent component memoize the props it's passing down.)

Also, remember that React runs way slower in dev builds. You can profile your app in development mode to see which components are rendering and why, and do some comparisons of relative time needed to render components in comparison to each other ("Component B took 3 times as long to render in this commit than component A" did). But, never measure absolute render times using a React development build - only measure absolute times using production builds! (or else Dan Abramov will have to come yell at you for using numbers that aren't accurate). Note that you'll need to use a special "profiling" build of React if you want to actually use the profiler to capture timing data from a prod-like build.

Context and Rendering Behavior

React's Context API is a mechanism for making a single user-provided value available to a subtree of components, Any component inside of a given <MyContext.Provider> can read the value from that context instance, without having to explicitly pass that value as a prop through every intervening component.

Context is not a "state management" tool. You have to manage the values that are passed into context yourself. This is typically done by keeping data in React component state, and constructing context values based on that data.

Context Basics

A context provider receives a single value prop, like <MyContext.Provider value={42}>. Child components may consume the context by rendering the context consumer component and providing a render prop, like:

Updating Context Values

React checks to see if a context provider has been given a new value when the surrounding component renders the provider. If the provider's value is a new reference, then React knows the value has changed, and that the components consuming that context need to be updated.

Note that passing a new object to a context provider will cause it to update:

In this example, every time ParentComponent renders, React will take note that MyContext.Provider has been given a new value, and look for components that consume MyContext as it continues looping downwards. When a context provider has a new value, every nested component that consumes that context will be forced to re-render.

Note that from React's perspective, each context provider only has a single value - doesn't matter whether that's an object, array, or a primitive, it's just one context value. Currently, there is no way for a component that consumes a context to skip updates caused by new context values, even if it only cares about part of a new value.

State Updates, Context, and Re-Renders

It's time to put some of these pieces together. We know that:

Calling setState() queues a render of that component

React recursively renders nested components by default

Context providers are given a value by the component that renders them

That value normally comes from that parent component's state

This means that by default, any state update to a parent component that renders a context provider will cause all of its descendants to re-render anyway, regardless of whether they read the context value or not!.

If we look back at the Parent/Child/Grandchild example just above, we can see that the GrandchildComponentwill re-render, but not because of a context update - it will re-render because ChildComponent rendered!. In this example, there's nothing trying to optimize away "unnecessary" renders, so React renders ChildComponent and GrandchildComponent by default any time ParentComponent renders. If the parent puts a new context value into MyContext.Provider, the GrandchildComponent will see the new value when it renders and use it, but the context update didn't causeGrandchildComponent to render - it was going to happen anyway.

Context Updates and Render Optimizations

Let's modify that example so that it does actually try to optimize things, but we'll add one other twist by putting a GreatGrandchildComponent at the bottom:

React sees that MyContext.Provider has a new context value, and thus any consumers of MyContext need to be updated

React will try to render MemoizedChildComponent, but see that it's wrapped in React.memo(). There are no props being passed at all, so the props have not actually changed. React will skip rendering ChildComponent entirely.

However, there was an update to MyContext.Provider, so there may be components further down that need to know about that.

React continues downwards and reaches GrandchildComponent. It sees that MyContext is read by GrandchildComponent, and thus it should re-render because there's a new context value. React goes ahead and re-renders GrandchildComponent, specifically because of the context change.

Because GrandchildComponentdid render, React then keeps on going and also renders whatever's inside of it. So, React will also re-render GreatGrandchildComponent.

That React Component Right Under Your Context Provider Should Probably Use React.memo

That way, state updates in the parent component will not force every component to re-render, just the sections where the context is read. (You could also get basically the same result by having ParentComponent render <MyContext.Provider>{props.children}</MyContext.Provider>, which leverages the "same element reference" technique to avoid child components re-rendering, and then rendering <ParentComponent><ChildComponent /></ParentComponent> from one level up.)

Note, however, that once GrandchildComponent rendered based on the next context value, React went right back to its default behavior of recursively re-rendering everything. So, GreatGrandchildComponent was rendered, and anything else under there would have rendered too.

This results in a very different set of performance characteristics than context. Yes, it's likely that fewer components will be rendering all the time, but React-Redux will always have to run the mapState/useSelector functions for the entire component tree every time the store state is updated. Most of the time, the cost of running those selectors is less than the cost of React doing another render pass, so it's usually a net win, but it is work that has to be done. However, if those selectors are doing expensive transformations or accidentally returning new values when they shouldn't, that can slow things down.

Differences between connect and useSelector

connect is a higher-order component. You pass in your own component, and connect returns a wrapper component that does all the work of subscribing to the store, running your mapState and mapDispatch, and passing the combined props down to your own component.

The connect wrapper components have always acted equivalent to PureComponent/React.memo(), but with a slightly different emphasis: connect will only make your own component render if the combined props it's passing down to your component have changed. Typically, the final combined props are a combination of {...ownProps, ...stateProps, ...dispatchProps}, so any new prop references from the parent will indeed cause your component to render, same as PureComponent or React.memo(). Besides parent props, any new references returned from mapState will also cause your component to render. . (Since you could customize how ownProps/stateProps/dispatchProps are merged, it's also possible to alter that behavior.)

useSelector, on the other hand, is a hook that is called inside of your own function components. Because of that, useSelector has no way of stopping your component from rendering when the parent component renders!.

This is a key performance difference between connect and useSelector. With connect, every connected component acts like PureComponent, and thus acts as a firewall to prevent React's default render behavior from cascading down the entire component tree. Since a typical React-Redux app has many connected components, this means that most re-render cascades are limited to a fairly small section of the component tree. React-Redux will force a connected component to render based on data changes, the next 2-3 components below it might render as well, then React runs into another connected component that didn't need to update and that stops the rendering cascade.

In addition, having more connected components means that each component is probably reading smaller pieces of data from the store, and thus is less likely to have to re-render after any given action.

If you're exclusively using function components and useSelector, then it's likely that larger parts of your component tree will re-render based on Redux store updates than they would with connect, since there aren't other connected components to stop those render cascades from continuing down the tree.

If that becomes a performance concern, then the answer is to wrap components in React.memo() yourself as needed, to prevent unnecessary re-renders caused by parent components.

Summary

React always recursively renders components by default, so when a parent renders, its children will render

Rendering by itself is fine - it's how React knows what DOM changes are needed

But, rendering takes time, and "wasted renders" where the UI output didn't change can add up

It's okay to pass down new references like callback functions and objects most of the time

But if you always pass new references down as props, React.memo() can never skip a render, so you may need to memoize those values

Context makes values accessible to any deeply nested component that is interested

Context providers compare their value by reference to know if it's changed

A new context values does force all nested consumers to re-render

But, many times the child would have re-rendered anyway due to the normal parent->child render cascade process

So you probably want to wrap the child of a context provider in React.memo(), or use {props.children}, so that the whole tree doesn't render all the time when you update the context value

When a child component is rendered based on a new context value, React keeps cascading renders down from there too

React-Redux uses subscriptions to the Redux store to check for updates, instead of passing store state values by context

Those subscriptions run on every Redux store update, so they need to be as fast as possible

React-Redux does a lot of work to ensure that only components whose data changed are forced to re-render

connect acts like React.memo(), so having lots of connected components can minimize the total number of components that render at a time

useSelector is a hook, so it can't stop renders caused by parent components. An app that only has useSelector everywhere should probably add React.memo() to some components to help avoid renders from cascading all the time.

Final Thoughts

Clearly, the whole situation is a lot more complex than just "context makes everything render, Redux doesn't, use Redux". Don't get me wrong, I want people to use Redux, but I also want people to clearly understand the behaviors and tradeoffs involved in different tools so they can make informed decisions about what's best for their own use cases.

Since everyone always seems to ask "When should I use Context, and when should I use (React-)Redux?", let me go ahead and recap some standard rules of thumb:

Use context if:

You just need to pass some simple values that don't change often

You have some state or functions that need to be accessed through part of the app, and you don't want to pass them as props all the way down

You want to stick with what's built in to React and not add additional libraries

Use (React-)Redux if:

You have large amounts of application state that are needed in many places in the app

The app state is updated frequently over time

The logic to update that state may be complex

The app has a medium or large-sized codebase, and might be worked on by many people

Please note that these are not hard, exclusive rules - they are just some suggested guidelines for when these tools might make sense! As always, please take some time to decide for yourself what the best tool is for whatever situation you're dealing with.

Overall, hopefully this explanation helps folks understand the bigger picture of what's actually going on with React's rendering behavior in various situations.