useMemo / useCallback
What they are in React
useMemo(factory, deps)— runfactory()whendepschanged since last commit; cache the return value so expensive work and stable object identities can skip repeats.useCallback(fn, deps)— cache the function reference itself whendepsare unchanged, so children that take amemocomparison or lists withkeydo not see a “new” handler every render.
Neither hook is for “hiding” side effects — only for derivations and referential stability.
React (reference)
const doubled = useMemo(() => count * 2, [count]);
const onInc = useCallback(() => setCount((c) => c + 1), [count]);(Exact bodies vary; the invariant is deps → invalidate cache.)
Vanilla implementation
Two tiny caches: memoDep / memoDoubled for the doubled value, cbDep / cachedIncrement for the increment function. When count matches the last deps, reuse; otherwise recompute / recreate.
export function mountLesson06(host) {
let count = 0;
let memoDep = NaN;
let memoDoubled = 0;
function readMemoDoubled() {
if (memoDep === count) return memoDoubled;
memoDep = count;
memoDoubled = count * 2;
return memoDoubled;
}
let cbDep = NaN;
let cachedIncrement = null;
function getIncrementCallback() {
if (cbDep === count && cachedIncrement) return cachedIncrement;
cbDep = count;
cachedIncrement = () => {
count += 1;
paint();
};
return cachedIncrement;
}
function paint() {
host.replaceChildren();
const doubled = readMemoDoubled();
const p = document.createElement('p');
p.textContent = `count: ${count} — memo doubled: ${doubled}`;
const q = document.createElement('p');
const cb = getIncrementCallback();
q.textContent = `same cb reference when count unchanged? ${cb === getIncrementCallback()}`;
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = 'increment';
btn.addEventListener('click', cb);
host.append(p, q, btn);
}
paint();
return () => host.replaceChildren();
}What this code is doing
readMemoDoubled— ifcounthas not changed, return the storedmemoDoubled; otherwise recompute. That isuseMemowithout React’s slot machinery.getIncrementCallback— whencountchanges, the increment closure must close over the new count path; here we recreate the function. Whencountis unchanged between two calls in the samepaint, the same function object is returned —useCallbackwith[count]deps.
Not in this lesson
Deep dependency compares, useCallback with [] stable forever. See ../REQUIREMENTS.md.