You're probably looking for the lazy initial state functional argument:
Lazy initial state
The initialState
argument is the state used during the initial render. In subsequent renders, it is disregarded. If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
But rather than just supply a link, let's examine the different behaviors of the code you showed (in addition to the lazy initial state functional argument).
A note about the code examples below:
They use a custom helper hook to record information about when component renders occur and when state values are updated. The outputs are serialized as JSON, so when a state value is undefined
, it is represented as null
in the JSON (because undefined
is not a valid JSON type). Because of the restrictions of Stack Overflow's code snippet mechanism, there's a lot of repeated boilerplate — the code in focus is in the body of the App
function.
Creating state initialized with a value:
const [state, setState] = useState(1);
In this case, the state is initialized once and never updated. The component is rendered one time:
<style>.json { background-color: hsla(0, 0%, 50%, 0.15); font-family: monospace; font-size: 1rem; padding: 0.5rem; }</style>
<div id="root"></div><script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.20.12/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const { useCallback, useRef, useState } = React;
function VisualJson ({ data }) {
const json = JSON.stringify(
data,
// undefined doesn't serialize, so null is used as a substitute:
(_key, value) => typeof value === "undefined" ? null : value,
2,
);
return (<pre className="json"><code>{ json }</code></pre>);
}
function useComponentUpdateMeta (state) {
const ref = useRef({
updates: [],
get totalRenders () {
return this.updates.filter(u => u.type === "render").length;
},
get totalStateUpdates () {
return this.updates.filter(u => u.type === "state").length;
},
});
ref.current.updates.push({ type: "render", time: performance.now(), state });
const recordStateUpdate = useCallback(state => {
ref.current.updates.push({ type: "state", time: performance.now(), state });
}, [ref]);
return [ref.current, recordStateUpdate];
}
function App () {
const [state, setState] = useState(1);
const [updateMeta, recordStateUpdate] = useComponentUpdateMeta(state);
return (<VisualJson data={ updateMeta } />);
}
const reactRoot = ReactDOM.createRoot(document.getElementById("root"));
reactRoot.render(<App />);
</script>
Creating state uninitialized, then updating in a useEffect
callback:
const [state, setState] = useState();
useEffect(() => {
setState(1);
}, []);
In this case, the state is initialized with an implicit undefined
value and the component is rendered. Then the useEffect
hook's callback function is invoked and the state is updated, causing the component to render a second time:
<style>.json { background-color: hsla(0, 0%, 50%, 0.15); font-family: monospace; font-size: 1rem; padding: 0.5rem; }</style>
<div id="root"></div><script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.20.12/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const { useCallback, useEffect, useRef, useState } = React;
function VisualJson ({ data }) {
const json = JSON.stringify(
data,
// undefined doesn't serialize, so null is used as a substitute:
(_key, value) => typeof value === "undefined" ? null : value,
2,
);
return (<pre className="json"><code>{ json }</code></pre>);
}
function useComponentUpdateMeta (state) {
const ref = useRef({
updates: [],
get totalRenders () {
return this.updates.filter(u => u.type === "render").length;
},
get totalStateUpdates () {
return this.updates.filter(u => u.type === "state").length;
},
});
ref.current.updates.push({ type: "render", time: performance.now(), state });
const recordStateUpdate = useCallback(state => {
ref.current.updates.push({ type: "state", time: performance.now(), state });
}, [ref]);
return [ref.current, recordStateUpdate];
}
function App () {
const [state, setState] = useState();
const [updateMeta, recordStateUpdate] = useComponentUpdateMeta(state);
useEffect(() => {
setState(1);
recordStateUpdate(1);
}, []);
return (<VisualJson data={ updateMeta } />);
}
const reactRoot = ReactDOM.createRoot(document.getElementById("root"));
reactRoot.render(<App />);
</script>
Creating state initialized with a lazily-invoked function:
const [state, setState] = useState(() => 1);
Similar to the first example: in this case, the state is initialized once (using a function whose return value becomes the initial state value) and never updated. The component is rendered one time:
<style>.json { background-color: hsla(0, 0%, 50%, 0.15); font-family: monospace; font-size: 1rem; padding: 0.5rem; }</style>
<div id="root"></div><script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.20.12/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const { useCallback, useRef, useState } = React;
function VisualJson ({ data }) {
const json = JSON.stringify(
data,
// undefined doesn't serialize, so null is used as a substitute:
(_key, value) => typeof value === "undefined" ? null : value,
2,
);
return (<pre className="json"><code>{ json }</code></pre>);
}
function useComponentUpdateMeta (state) {
const ref = useRef({
updates: [],
get totalRenders () {
return this.updates.filter(u => u.type === "render").length;
},
get totalStateUpdates () {
return this.updates.filter(u => u.type === "state").length;
},
});
ref.current.updates.push({ type: "render", time: performance.now(), state });
const recordStateUpdate = useCallback(state => {
ref.current.updates.push({ type: "state", time: performance.now(), state });
}, [ref]);
return [ref.current, recordStateUpdate];
}
function App () {
const [state, setState] = useState(() => 1);
const [updateMeta, recordStateUpdate] = useComponentUpdateMeta(state);
return (<VisualJson data={ updateMeta } />);
}
const reactRoot = ReactDOM.createRoot(document.getElementById("root"));
reactRoot.render(<App />);
</script>
One more consideration is this: when using Strict Mode in development, React will unmount and re-mount your entire app's tree of nodes, effectively causing an extra render.
References:
<style>.json { background-color: hsla(0, 0%, 50%, 0.15); font-family: monospace; font-size: 1rem; padding: 0.5rem; }</style>
<div id="root"></div><script src="https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.20.12/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const { StrictMode, useCallback, useEffect, useRef, useState } = React;
function VisualJson ({ data }) {
const json = JSON.stringify(
data,
// undefined doesn't serialize, so null is used as a substitute:
(_key, value) => typeof value === "undefined" ? null : value,
2,
);
return (<pre className="json"><code>{ json }</code></pre>);
}
function useComponentUpdateMeta (state) {
const ref = useRef({
updates: [],
get totalRenders () {
return this.updates.filter(u => u.type === "render").length;
},
get totalStateUpdates () {
return this.updates.filter(u => u.type === "state").length;
},
});
ref.current.updates.push({ type: "render", time: performance.now(), state });
const recordStateUpdate = useCallback(state => {
ref.current.updates.push({ type: "state", time: performance.now(), state });
}, [ref]);
return [ref.current, recordStateUpdate];
}
function App () {
const [state, setState] = useState();
const [updateMeta, recordStateUpdate] = useComponentUpdateMeta(state);
useEffect(() => {
setState(1);
recordStateUpdate(1);
}, []);
return (<VisualJson data={ updateMeta } />);
}
const reactRoot = ReactDOM.createRoot(document.getElementById("root"));
reactRoot.render(
<StrictMode>
<App />
</StrictMode>
);
</script>