🐏 How React memoization works

You wrapped a component in React.memo, and it still re-renders every time its parent does. It’s one of the most common surprises in React, and it comes down to what JSX actually creates on every render.

First you should know whats happening when using React and JSX

import React from "react";

function SomeChildren() {
	/* ... */
	return null; /* ... */
}

function LinkWrapper(props) {
	/* ... */
	return null; /* ... */
}

function App() {
	return (
		<LinkWrapper uselink>
			<SomeChildren />
		</LinkWrapper>
	);
}

When compiling JSX, Function App becomes

import React from "react";

function App() {
	return React.createElement(
		LinkWrapper, // component
		{ useLink: true }, // props
		React.createElement(SomeChildren), // children
	);
}

And at runtime when React calls your function, here is what your function will return Replaced App return with what kind of data React.createElement is returning.

function App() {
	return {
		$$typeof: Symbol(react.element),
		type: LinkWrapper,
		props: {
			useLink: true,
			children: {
				$$typeof: Symbol(react.element),
				type: SomeChildren,
				props: {},
			},
		},
	};
}

So at every call, React will always get a new definition of your App, This will trigger to get the definition for all tree of components. Note: This will not actually render to DOM anything. React just needs to know if anything changed.

Now for example you use React.memo to memoize your LinkWrapper

const LinkWrapper = React.memo((props) => {
	return null; /* ... */
});

This will make React to receive previous LinkWrapper return value if props were not changed. Note: By default it will only shallowly compare complex objects in the props object.

Now let’s come back at our App

function App() {
	return (
		<LinkWrapper uselink>
			<SomeChildren />
		</LinkWrapper>
	);
}

As I explained above <SomeChildren /> always will return a new React definition. This means that using React.memo on LinkWrapper will not have any effect. Because children always will be a new definition.

If you want to also memoize children you will have to do it manually.

function App() {
	const memoChildren = React.useMemo(() => <SomeChildren />, []);
	return <LinkWrapper uselink>{memoChildren}</LinkWrapper>;
}

This can also be wrote as

function App() {
	const memoChildren = React.useMemo(() => <SomeChildren />, []);
	return <LinkWrapper uselink children={memoChildren} />;
}

Now memoChildren will always have same value between re-renders Now LinkWrapper will also see that children did not change, and will return last memoized value without calling the function again

Created At
3/31/2020
Updated At
9/15/2023
Published At
3/31/2020