How to Initialize State Lazily with useState

Initialize useState lazily by passing an initializer function. React runs it once, so expensive setup is not repeated on every render.

5 min read

To initialize state lazily with useState, pass a function instead of calling it yourself. React runs that initializer only during the first render, so expensive setup is not repeated on every update. The result of the function becomes the initial value.

Why lazy initialization helps

useState accepts an initial value. The problem appears when computing that value is slow, like building a large list or reading from storage.

The code that produces the value would otherwise run on every render, even though React keeps only the first result. Every keystroke or click re-runs the component, so that setup cost would repeat with each update.

Lazy initialization fixes this by deferring the work. React calls your function once, stores what it returns, and ignores the argument on later renders. The result is identical on screen; only the cost changes.

Pass an initializer function

The difference is one pair of parentheses. Pass the function itself, not the result of calling it.

App.jsxApp.jsx
import { useState } from "react";
 
function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos);
}

Here React calls createInitialTodos once during the first render and stores its array as the initial state. On later renders, the function is not called again.

The incorrect version calls the function every render:

App.jsxApp.jsx
const [todos, setTodos] = useState(createInitialTodos());

The result is the same on screen, but the setup work runs again and again for no reason. For a function that loops fifty times, the difference is small. For one that reads a large file or computes a heavy object, it adds up quickly.

Think of it as deferring the calculation. React stores a note to run the function when it first needs the value, instead of running it right now.

What the initializer looks like

The initializer is a plain function with no arguments. It builds and returns the starting value.

App.jsxApp.jsx
function createInitialTodos() {
  const list = [];
  for (let i = 0; i < 50; i++) {
    list.push({ id: i, title: "Item " + (i + 1) });
  }
  return list;
}

React stores whatever this function returns as the first value of the state. The function must be pure: it should not change anything outside itself, because React may call it more than once in development.

Initial value from props

An initializer receives no arguments, so it cannot react to props changing. If the starting value depends on a prop, the state only captures that prop's first value.

App.jsxApp.jsx
const [draft, setDraft] = useState(() => createDraft(initialText));

When initialText changes later, draft does not reset, because React already stored the first result. If a value should always follow a prop, compute it during render instead of copying it into state.

When lazy initialization is not needed

Most state starts from a simple value, like a number, a string, or a short object. For those, pass the value directly.

App.jsxApp.jsx
const [count, setCount] = useState(0);

Reserve the function form for setup that actually costs something. A boolean, an empty string, or a small constant is clear enough as a direct value, and the function form only adds noise. The useState guide covers the basic value form, and the same pass-a-function idea appears in updater functions, where React calls your function with the latest value.

Common mistakes

  • Calling the initializer yourself, which defeats the purpose and runs the work every render.
  • Putting side effects in the initializer, which Strict Mode can run twice in development.
  • Using lazy initialization for trivial values where a plain value is clearer.

If the initial value is itself a function, wrap it in an arrow so React stores the function instead of calling it. The initializer runs only once, but it must still be deterministic so development checks do not change the result. A pure initializer passes those checks every time.

What to learn next

Lazy initialization pairs naturally with lists built in state, which you can then update with the array operations. Next, learn how React groups your updates into one render, and why that timing matters.

Rune AI

Rune AI

Key Insights

  • Pass an initializer function to useState, not its result.
  • React runs the initializer only during the first render.
  • Calling it yourself runs the work on every render.
  • Keep the initializer pure with no side effects.
  • Use lazy initialization only when the setup is expensive.
RunePowered by Rune AI

Frequently Asked Questions

What does lazy initialization mean in React?

It means passing a function to useState that React calls only during the first render. The return value becomes the initial state.

Why not just call the function myself?

If you write useState(createTodos()), the function runs on every render even though React only keeps the first result. Passing it without parentheses avoids that waste.

Does React call my initializer twice?

In development, Strict Mode may call the initializer twice to expose side effects. The result of one call is discarded, so a pure initializer behaves the same.

Conclusion

Pass the function itself to useState to initialize lazily. React calls it once during the first render, so expensive setup is not repeated on every update.