In computing, memoization or memoisation
is an optimization technique used primarily
to speed up computer programs by storing
the results of expensive function calls and Â
returning the cached result when the same
inputs occur again. Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â
                                                    — wikipedia
It's important to memoize heavy computations as well as arrays and object creations so that they don't get re-created on every render. A re-render occurs when state changes, redux dispatches some action, or when the user types into a text input (re-render for every single key press). You don't want to run a lot of operations in those renders for very obvious reasons. So no heavy filtering, no list operations, etc.
If variables get re-created each render, they won't maintain reference equality. When they don't maintain reference equality, React thinks that you passed a different variable to subcomponents, and will trigger re-renders for those too. Often those variables even go over the Bridge and make your app slow.
When a React component re-renders, it compares the previous props to the current props and checks if they are shallow-equal.
Assuming you create two variables, both of the same type and same value, they are not guaranteed to be equal. Only "value types" can be shallow-compared successfully, while "reference types" have to be reference equal. Here's a code example to demonstrate this:
const i1 = 7;
const i2 = 7;
const equal = i1 === i2;
In this case, numbers are "value types" and can be compared by value. They are equal, the variable equal
is therefore true
. This applies to numbers, booleans and strings.
const o1 = { x: 7 };
const o2 = { x: 7 };
const equal = o1 === o2;
In this case, two objects get created. Since objects can get quite complex and go deeper than a single value, they are only compared for reference equality. In this case, we have two objects, and o1
is a reference to the first object, while o2
is a reference to the second object. This means, they are not reference-equal, equals
is therefore false
. This applies to objects, arrays and functions.
In React your component's render()
function (or simply a function component's function) is executed on each render. If you create objects in this function, they will be re-created on every single render. This means when you create an object in the first render, it is not reference-equal to the second render. For this very reason, memoization exists.
Use the useMemo
hook to memoize arrays and objects which will keep their reference equality (and won't get re-created on each render) as long as the dependencies (second argument) stay the same. Use the useCallback
hook to memoize a function.
In general, function components can be optimized more easily due to the concept of hooks. You can however apply similar techniques for class components, just be aware that this will result in a lot more code.
While animations and performance intensive tasks are scheduled on native threads, your entire business logic runs on a single JavaScript thread, so make sure you're doing as little work as possible there. Doing too much work on the JavaScript thread can be compared to a high ping in a video game - you can still look around smoothly, but you can't really play the game because every interaction takes too long.
Here are a few examples to help you avoid doing too much work on your JavaScript thread:
return <View style={[styles.container, { backgroundColor: 'red' }]} />
const style = useStyle(() => [styles.container, { backgroundColor: 'red' }], []);
return <View style={style} />
- Reanimated styles from
useAnimatedStyle
, as those have to be dynamic.
See useStyle.ts
Using filter
, map
or other array operations in renderers will run the entire operation again for every render.
return <Text>{users.filter((u) => u.status === "online").length} users online</Text>
const onlineCount = useMemo(() => users.filter((u) => u.status === "online").length, [users]);
return <Text>{onlineCount} users online</Text>
You can also apply this to render multiple React views with .map
. Those can be memoized with useMemo
too.
return <View onLayout={(layout) => console.log(layout)} />
const onLayout = useCallback((layout) => {
console.log(layout);
}, []);
return <View onLayout={onLayout} />
Make sure to also think about other calls in the renderer, e.g. useSelector
, useComponentDidAppear
- wrap the callback there too!
return <PressableOpacity onPress={() => logoutUser()} />
return <PressableOpacity onPress={logoutUser} />
return <RecyclerListView scrollViewProps={{ horizontal: true }} />;
const scrollViewProps = useMemo(() => ({ horizontal: true }), []);
return <RecyclerListView scrollViewProps={scrollViewProps} />;
function MyComponent() {
return <RecyclerListView scrollViewProps={{ horizontal: true }} />;
}
const SCROLL_VIEW_PROPS = { horizontal: true }
function MyComponent() {
return <RecyclerListView scrollViewProps={SCROLL_VIEW_PROPS} />;
}
This applies to objects as well as functions which don't depend on the component's state or props. Always use this, since it's even more efficient than useMemo
and useCallback
.
const [me, setMe] = useState(users.find((u) => u.id === myUserId));
const [me, setMe] = useState(() => users.find((u) => u.id === myUserId));
The useState
hook accepts an initializer function. While the first example ("Bad") runs the .find
on every render, the second example only runs the passed function once to initialize the state.
When writing new components I always put a log statement in my render function to passively watch how often my component re-renders while I'm working on it. In general, components should re-render as little as possible, and if I see a lot of logs appearing in my console I know I did something wrong. It's a good practice to put this function in your component once you start working on it, and remove it once done.
function ComponentImWorkingOn() {
// code
console.log('re-rendering ComponentImWorkingOn!');
return <View />;
}
You can also use the why-did-you-render library to find out why a component has re-rendered (prop changes, state changes, ...) and possibly catch mistakes early on.
memoize!!