The React State
By Flavio Copes
Learn how React state works with useState in function components, when to lift state up, and how unidirectional data flow keeps updates predictable.
State is data a component owns and can change over time. When state changes, React re-renders that component and its children.
In a function component you add state with the useState hook. Class components have their own way, this.state and setState(), and I cover that at the end of the post since you will still find it in a lot of code.
Setting state with useState
Pass the initial value to useState. React returns the current value and a setter function:
import { useState } from 'react'
const BlogPostExcerpt = () => {
const [clicked, setClicked] = useState(false)
return (
<div>
<h1>Title</h1>
<p>Description</p>
<p>Clicked: {clicked ? 'yes' : 'no'}</p>
<button onClick={() => setClicked(true)}>Mark as clicked</button>
</div>
)
}
You can call useState more than once for more variables. Keep those calls at the top level of the component, not inside if or loops. More on that in the React Hooks introduction.
Don’t mutate state directly
Do not assign to the state variable yourself:
clicked = true // wrong
This does not even run: clicked is a const, so you get a TypeError. And if you hold an object or an array in state, mutating it in place (items.push(x)) does run, but React never finds out, so nothing re-renders.
Use the setter React gave you:
setClicked(true)
That is how React knows something changed and schedules a re-render (and any DOM update that follows).
The state variable does not change right after you call the setter. It is a plain value captured by the current render. The new value shows up on the next render. So if the next value depends on the previous one, pass a function to the setter instead of reading the variable:
setClicked(prev => !prev)
React calls that function with the latest state, which matters when you call the setter more than once in the same event handler. React batches all the state updates in a handler (since React 18, also the ones in timeouts and promises) and re-renders once at the end. With setCount(count + 1) twice in a row you get +1, because count is the same value both times. With setCount(prev => prev + 1) twice you get +2.
Unidirectional Data Flow
A piece of state is always owned by one component. Data that depends on that state can only affect components below it: its children.
Changing state in a component never updates its parent, its siblings, or unrelated components. Only that component and the children that receive the new values as props.
That is why we often move state up the tree when more than one component needs the same data.
Moving the State Up in the Tree
Because of unidirectional data flow, if two components need to share state, the state needs to live in a common ancestor.
Many times the closest ancestor is the best place. That is a guideline, not a hard rule.
The parent keeps the state and passes values down as props. It can also pass a function so a child can request an update:
import { useState } from 'react'
const Converter = () => {
const [currency, setCurrency] = useState('€')
const handleChangeCurrency = () => {
setCurrency(prev => (prev === '€' ? '$' : '€'))
}
return (
<div>
<Display currency={currency} />
<CurrencySwitcher
currency={currency}
handleChangeCurrency={handleChangeCurrency}
/>
</div>
)
}
const CurrencySwitcher = ({ currency, handleChangeCurrency }) => {
return (
<button onClick={handleChangeCurrency}>
Current currency is {currency}. Change it!
</button>
)
}
const Display = ({ currency }) => {
return <p>Current currency is {currency}.</p>
}
The parent owns currency. The children only read props (and call the callback they were given). Data still flows down.
Class components and setState (legacy)
Older React code stores state on class instances. You initialize it in the constructor and update it with setState:
class BlogPostExcerpt extends React.Component {
constructor(props) {
super(props)
this.state = { clicked: false }
}
render() {
return (
<div>
<p>Clicked: {this.state.clicked ? 'yes' : 'no'}</p>
<button onClick={() => this.setState({ clicked: true })}>
Mark as clicked
</button>
</div>
)
}
}
Don’t write this.state.clicked = true: it changes the object, but React does not know and does not re-render. Call this.setState({ clicked: true }) so React schedules the update.
Two differences from useState. First, setState merges the object you pass into the existing state, so you can pass just the keys that change and the others stay as they are. Second, the update is not applied right away: this.state still holds the old value on the line after the call. If the new value depends on the old one, pass a function, like with hooks: this.setState(state => ({ clicked: !state.clicked })).
Classes still work in React 19 and there is no need to rewrite them. For new components I use function components and useState.
Want me to talk about your product? You can sponsor this site.