Inside a React function component you write `const defaultTags = [];` and pass it to a child wrapped in React.memo. Why is that a different array on every render, and what is the simplest way to make it the same array every time?
answer
- the body runs again every render
- const is not a cache
- literals allocate, they do not reuse
- module scope outlives every render
basics
~20 sA function component's body runs again on every render, so the array literal is evaluated again and produces a brand-new array. Move it to a module-level constant outside the component and every render passes the identical array.
solid answer
~50 sThere is nothing special about a component body — it is a function that React calls again for each render, top to bottom. `const defaultTags = [];` is an expression, not a cache, so each call allocates a fresh array. The memoized child compares props with `Object.is`, sees a different reference, and re-renders even though the array is always empty. The simplest fix for a value that never changes is to hoist it above the component: `const DEFAULT_TAGS = [];` at module scope is created once when the module loads and shared by every render and every instance. `useMemo(() => [], [])` also works but pays for a hook and a dependency array to produce a constant — reach for it only when the value depends on props or state. Primitives need none of this: a fresh `0` or `''` is already equal by value.
code
javascript · 14 linesimport { memo } from 'react';
// created once when the module loads, shared by every render
const DEFAULT_TAGS = [];
const Tags = memo(function Tags({ tags }) {
return <span>{tags.length} tag(s)</span>;
});
export function Card({ tags }) {
// writing `const DEFAULT_TAGS = [];` here instead would allocate
// a new array on every render and defeat the memo above
return <Tags tags={tags ?? DEFAULT_TAGS} />;
}go deeper
Be able to say that React calls your component function again for each render, so every literal in the body is allocated again, and that hoisting an invariant value above the component gives one shared reference.
Explain the consequence precisely: a new reference each render fails Object.is, so a memoized child never bails out and a dependency array never settles. Contrast module scope, useMemo and useRef and say what each is for.
Show judgment about which values deserve a stable identity at all, and flag the hazard of a shared mutable module constant. Prefer flattening props to primitives over adding stabilization the next refactor can quietly break.
Set the convention: where shared constants live, how they are marked read-only, and when the team should stop hand-stabilizing identities because build-time auto-memoization or a different component shape removes the need.
## The body is a function, not a declaration block Beginners often read a component body as setup code that runs once. It does not. React calls the component function for every render, so every line in the body executes again — including variable declarations. `const` controls **rebinding**, not allocation: it prevents you reassigning the name inside one call, and says nothing across calls. ```jsx function Card({ tags }) { const defaultTags = []; // new array, every render return <Tags tags={tags ?? defaultTags} />; } ``` Each call allocates a new empty array. The arrays are equal in content and different in identity. ## Why identity matters downstream A child wrapped in `React.memo` compares the previous props with the next ones using `Object.is` per prop key, which for arrays and objects is reference identity. Passing a new empty array every render means the comparison fails every render, the memo never bails out, and the optimization is dead weight — the comparison cost with none of the benefit. The same identity check governs hook dependency arrays, so an unstable value in a dependency list makes the effect or memo re-run every render too. ## The simplest fix: module scope Hoist the constant above the component: ```jsx const DEFAULT_TAGS = []; function Card({ tags }) { return <Tags tags={tags ?? DEFAULT_TAGS} />; } ``` The array is now allocated once when the module is first evaluated, and every render of every instance of `Card` passes that same reference. `Object.is` succeeds, the memo bails out, and no hook was needed. Use `SCREAMING_CASE` or another convention to signal that it is shared and must never be mutated — because it *is* shared, mutating it would leak into every consumer. ## When a hook is the right tool instead Module scope only works for values that do not depend on anything render-specific. As soon as the value is derived from props or state, it must be recomputed when those change, and that is what `useMemo` is for: ```jsx const visibleTags = useMemo(() => tags.filter(Boolean), [tags]); ``` `useMemo(() => [], [])` for a literal constant is legal but wasteful: it allocates a hook slot and a dependency array to reproduce what a module constant gives for free. Prefer the constant when the value is truly invariant. `useRef` is a third option — `useRef([]).current` gives one array per component instance rather than one per module — which matters when the value must be per-instance and mutable (a scratch buffer, a set of ids for one component). For a shared read-only default, the module constant is simpler. ## Primitives are not affected `Object.is('', '')` and `Object.is(0, 0)` are `true`, because primitives compare by value rather than by allocation. Writing `const emptyLabel = '';` in the body every render costs nothing in identity terms. This is why flattening an object prop into primitive props sometimes removes an identity problem entirely — `padding={8}` never destabilizes a memo, while `style={{ padding: 8 }}` always does. ## The habit to build When you pass an object, array or function to a memoized child or into a dependency array, ask one question: *where was this value created, and does that place run again on every render?* If the answer is the component body and the value never varies, it belongs above the component.
- When is `useMemo(() => [], [])` a better choice than a module-level constant?Almost never for a literal constant — it costs a hook slot and a dependency array to reproduce what module scope gives free. `useMemo` earns its place once the value depends on props or state, like `useMemo(() => tags.filter(Boolean), [tags])`, where the result must change when the inputs do. Use `useRef` instead when you need one value per component instance rather than one per module.
- Does the same reasoning apply to a default string or number written in the body?No. Primitives compare by value, so `Object.is('', '')` and `Object.is(0, 0)` are both true no matter how many times the literal is evaluated. Only objects, arrays and functions carry identity, which is why flattening an object prop into primitive props can remove an identity problem instead of managing it.
saying these in an interview costs you the question
- Thinking const means the value is created once
- Believing React caches variables declared in the body
- Mutating a shared module constant that others read
- Reaching for useMemo to produce a plain constant