This is a contrived example of a question that came up in code review the other day about memoizing context values in React. Let's say I have a hook which returns a memoized value of an expensive operation like so
import { useMemo } from 'react'
const ONE_MILLION = 1_000_000
const ZERO_TO_ONE_MILLION = Array(ONE_MILLION).fill(0).map((_, i) => i)
export type NumberSelector = (n: number) => boolean
/**
* This hook returns a memoized sequence of numbers from 0 to 1,000,00
* for which the given selector returns true.
*/
export function useSequence(selector: NumberSelector): number[] {
return useMemo(() => ZERO_TO_ONE_MILLION.filter(selector), [selector])
}
Then let's say we want a context which contains several sequences
import { createContext, PropsWithChildren } from 'react'
import { selectOddNumber, selectEvenNumber, selectPowerOfTwo } from './selectors'
import { useSequence } from './useSequence'
export type Numbers = {
odds: number[]
evens: number[]
powersOfTwo: number[]
}
export const NumberContext = createContext<Numbers>({
odds: [],
evens: [],
powersOfTwo: [],
})
export function NumberProvider({ children }: PropsWithChildren<{}>) {
// compute expensive calculations
const odds = useSequence(selectOddNumber)
const evens = useSequence(selectEvenNumber)
const powersOfTwo = useSequence(selectPowerOfTwo)
return (
<NumberContext.Provider value={{ odds, evens, powersOfTwo }}>
{children}
</NumberContext.Provider>
)
}
My coworker mentioned in the code-review that I should also memoize the value passed to the provider, something like:
const memoizedValue = useMemo(() => ({ odds, evens, powersOfTwo }), [odds, evens, powersOfTwo])
return (
<NumberContext.Provider value={memoizedValue}>
{children}
</NumberContext.Provider>
)
Otherwise this would cause the sub-tree to re-render unnecessarily. However, I am not so convinced since our application is mainly using functional components which will re-render even if the props stay the same.
My main two questions are:
- Does React always re-render the subtree unless specified otherwise with
memo
, etc. - Should we always memoized the value to a context provider?
Just for a little more context we primarily interact with a hook
import { useContext } from 'react'
import { NumberContext, Numbers } from './index'
export function useNumbers(): Numbers {
return useContext(NumberContext)
}
Also here is an example of the code, thanks!