useState
useState
is a Hook that allows you to have state variables in functional components. When the state
changes, the component re-renders.
const [state, setState] = useState(initialState);
Caveats
useState
is a Hook, so you can only call it at the top level of a functional component or your own hook. It can not be called inside loops, conditions, or nested functions. If you need that, extract a new component and move the state into it.setState
doesn't immediately mutate the state. It schedules an update to the component. This is to improve performance and is the reason why you can't use the value of the state immediately after callingsetState
. Instead, useuseEffect
to perform side effects after the state has been updated.- In Strict Mode, React will call your initializer function twice in order to help you find accidental impurities. This is development-only behavior and won't affect the production build.
Two ways to update the state:
setState(newState); // used when you don't need the previous state
setState((prevState) => newState); // used when you need to compute the next state based on the previous state
Example
function CountApp() { const [count, setCount] = useState(0); function updateCount() { setCount((count) => count + 1); } return ( <div> <p>You clicked {count} times</p> <button onClick={updateCount}>Click me</button> </div> ); }
Caveats
- The
setState
function only updates the state variable for the next render. If you read the state variable after calling the set function, it will return the old value that was currently on the screen. - React will compare the current value and the next value of the state variable to decide whether to re-render the component. If you are updating the state variable with the same value, React will not re-render the component. React uses
Object.is()
for the comparison. - React batches multiple
set
function calls into a single update for better performance. React version 17 with legacyrender
will only batch the update inside an event handler anduseEffect
, but React version 18createRoot
will batch the update in all cases. More info on how automatic batching behavior works.
Example 1: setState
only updates the state for the next render
function Caveats1() { const [count, setCount] = useState(0); function updateCount() { setCount((count) => count + 1); console.log('count', count); // return count = 0 instead of count = 1 } return ( <div> <p>You clicked {count} times</p> <button onClick={updateCount}>Click me</button> </div> ); }
Example 2: React does not re-render if the next state is the same (compare by Object.is()
)
function Caveats2() { const [count, setCount] = useState(0); function updateCount() { setCount(0); // doesn't re-render because the value is the same } return ( <div> <p>You clicked {count} times</p> <button onClick={updateCount}>Click me</button> </div> ); }
const PERSON = { firstName: 'Nguyen', lastName: 'A', }; function Caveats2() { const [person, setPerson] = useState(PERSON); function updateByValue() { setCount((prev) => { prev.fistName = 'Tran'; prev.lastName = 'B'; return prev; }); // doesn't re-render because the object reference is the same } function updateByReference() { setCount((prev) => { return { firstName: 'Tran', lastName: 'B', }; }); // re-render because the object reference is different } return ( <div> <p>name: {person.lastName + '' + person.firstName}</p> <button onClick={updateByValue}>Update Object by Value</button> <button onClick={updateByReference}>Update Object by Reference</button> </div> ); }
Example 3: Automatic batching
function App() { const [count, setCount] = useState(0); const [flag, setFlag] = useState(false); function handleClick() { setCount((c) => c + 1); // Does not re-render yet setFlag((f) => !f); // Does not re-render yet // React will only re-render once at the end (that's batching!) } return ( <div> <button onClick={handleClick}>Next</button> <h1 style={{ color: flag ? 'blue' : 'black' }}>{count}</h1> </div> ); }
function App() { const [count, setCount] = useState(0); const [flag, setFlag] = useState(false); function updateFlag() { setFlag((f) => !f); // Does not re-render yet // React will only re-render once at the end (that's batching!) } useEffect(() => { setCount(0); setCount(1); setCount(2); setCount((prev) => prev + 1); // 3 setCount((prev) => prev + 1); // 4 // React will only re-render once at the end (that's batching!) }, [flag]); return ( <div> <button onClick={updateFlag}>Next</button> <h1 style={{ color: flag ? 'blue' : 'black' }}>{count}</h1> </div> ); }
Other use cases
- Use State as a key to reset a component
function ResetComponent() { const [key, setKey] = useState(0); function reset() { setKey((prev) => prev + 1); } return ( <div> <button onClick={reset}>Reset</button> <Child key={key} /> </div> ); }
- Use State to store a previous value passed as a prop
function Child({ value }) { const [prevValue, setPrevValue] = useState(null); let trend = 'no change'; useEffect(() => { if (prevValue === null || value === prevValue) { trend = 'no change'; else if (value > prevValue) { trend = 'up'; } else { trend = 'down'; } setPrevValue(value); }, [value]); return <p>Trend: {trend}</p>; }