Custom useMemo Hook
Prompt
Implement your own version of React's useMemo hook called useCustomMemo. It should accept a function and a dependency array, and return a memoized value that only recalculates when the dependencies change. You can use other React hooks (useRef, useState, etc.) but not useMemo itself.
Playground
Solution
Explanation
useMemo is one of React's most important performance tools. Building your own version teaches you what's actually happening under the hood when you write useMemo(() => expensiveCalculation(a, b), [a, b]).
The full solution
function useCustomMemo(fn, dependencies) {
const ref = useRef({
dependencies: [],
value: undefined,
initialized: false,
});
const depsChanged =
!ref.current.initialized ||
dependencies.length !==
ref.current.dependencies.length ||
dependencies.some(
(dep, index) =>
!Object.is(dep, ref.current.dependencies[index])
);
if (depsChanged) {
ref.current.value = fn();
ref.current.dependencies = dependencies;
ref.current.initialized = true;
}
return ref.current.value;
}The idea
We need to remember the result of a calculation and only redo it when the inputs change. To "remember" between renders, we use useRef (not useState, because updating a ref doesn't trigger another render). Our ref stores three things: the cached result, the dependencies from the last time we calculated, and a flag for whether we've ever calculated at all.
Every time the component renders, we compare the new dependencies with the saved ones. If anything changed, we recalculate. If nothing changed, we skip the work and return the cached result.
How the comparison works
const depsChanged =
!ref.current.initialized ||
dependencies.length !== ref.current.dependencies.length ||
dependencies.some(
(dep, index) =>
!Object.is(dep, ref.current.dependencies[index])
);We're asking three questions in order: Have we ever run the calculation? Did the number of dependencies change? Did any individual value change? If any answer is "yes," we recalculate.
We use Object.is for the comparison instead of === because it handles a tricky edge case: NaN === NaN is false in JavaScript (which makes no sense), but Object.is(NaN, NaN) is true. This is the same comparison React uses internally for all its dependency checks.
Tracing through a real example
Let's say we have useCustomMemo(() => count * 2, [count]):
First render (count = 0): Never initialized, so we calculate: 0 * 2 = 0. Save the result and the dependency [0].
Second render (parent re-renders, count is still 0): Compare [0] with saved [0]. Same. Skip the calculation, return cached 0. This is the whole point — we avoided work we didn't need to do.
Third render (user clicks, count is now 1): Compare [1] with saved [0]. Different. Recalculate: 1 * 2 = 2. Save the new result and dependency.
The function only runs when the inputs actually change. For simple math like multiplication this doesn't matter much, but for expensive operations (sorting large arrays, complex filtering, generating derived data), skipping unnecessary recalculations can make your app feel noticeably faster.