What is lazy initialisation in useState, and when should you use it?
Pass a function to useState, as in useState(() => compute()). React runs it only on the first render. Without the function form, the expensive call runs on every render, even though its result is ignored after the first.
Explain it simply
Imagine you pack a full suitcase every morning but only ever use the first one. Wasteful. useState(compute()) calls compute() on every render, because JavaScript evaluates the argument before React even sees it. React then ignores the result after the first time, but the work has already been done.
Passing () => compute() hands React a recipe instead of a finished dish. React cooks it once, on mount, and never again for that component instance.
This matters when the starting value is costly: parsing a large JSON string from localStorage, filtering a big array, or building a lookup table. For cheap values like 0, "" or false, the plain form is perfectly fine and easier to read.
Example
function Notes() {
// runs JSON.parse on every render
const [a] = useState(JSON.parse(localStorage.getItem("notes") || "[]"));
// runs only once, on the first render
const [b] = useState(() => JSON.parse(localStorage.getItem("notes") || "[]"));
return <p>{b.length} notes</p>;
}What to say in the interview
"The argument to useState is evaluated on every render unless it is a function. With a lazy initialiser, React calls it only on the first render. I use it for work like parsing localStorage or building a large structure, and pass a plain value for cheap defaults."
Common mistakes
- Passing the result of the call, useState(init()), instead of the function itself, useState(init).