Reputation: 5121
const [state, set_state] = useState(1);
vs
const [state, set_state] = useState();
useEffect(() => {
set_state(1);
}, [])
Is there any real difference between the two in terms of in what cycle the update actually happens? Does using a useEffect
cause an additional cycle, etc?
Upvotes: 0
Views: 241
Reputation: 33691
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 asnull
in the JSON (becauseundefined
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 theApp
function.
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/[email protected]/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/[email protected]/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>
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/[email protected]/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/[email protected]/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>
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/[email protected]/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/[email protected]/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/[email protected]/umd/react.development.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.development.js"></script><script src="https://cdn.jsdelivr.net/npm/@babel/[email protected]/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>
Upvotes: 1