See Our Public Workshops:
3-part Series
- How is state related to the declarative approach in React?
- ⭐️ React State: Common Questions?
- Derived State.
When I'm teaching React's state to people who are seeing it for the first time, they usually have the same set's of questions:
Can I use useState
more than once?
Yes. In fact that's how it was designed to be used. You just can't conditionalize the calls to useState
because the call order matters to React. The number of times you called useState
is tracked and React expects the same amount of calls each time. Otherwise they'll return the wrong information to you. From React's perspective, they don't know if you're conditionalizing a call to useState
, so the built-in linter will check to see if you are.
For example you can't do this:
const [count, setCount] = useState(0)if (count > 3) {const [otherState, setOtherState] = useState(null)}const [error, setError] = useState(null)
If you could do the example above, then sometimes the error
call to useState
would be second and sometimes it would be third. That would be a problem for React.
This always brings up another question...
Can I just call useState
once and store all the state as an object?
Yes, but you might not want to.
With class components we would keep all of our state in one object. Then when we changed state with setState
, we would provide an object and the API would "merge" its contents into the existing state for us. This doesn't happen with setting state in function components with useState
.
If you did try to do it like this:
const [state, setState] = useState({count: 0,error: null,})function add() {setState({ count: state.count + 1 })}
You'd end up destroying state you didn't mean to, like the error
in this case because calling setState
will <u>replace</u> all existing state that you're storing in that particular call to useState
.
If you choose to organize your state into a single object, you might have good reasons to. Just be sure to take care of preserving all the state when you make changes:
// The spread operator here takes all the existing state// and does a shallow copy into this new object:setState({ ...state, count: state.count + 1 })
If this doesn't make any sense, especially when I start talking about class components, that's okay. Maybe you don't need to know class components. I do go into more detail about how this works in the video above if you need more clarity.
What about using useReducer instead?
The normal convention you'll hear is:
"If your state is complex, like a deeply nested object, useReducer
is probably better than useState
for this type of state."
That's the typical thing you'll hear in the React community. But we have more on that later in this document.
Is that the only reason to call useState separately?
No. Even the official docs give you a hint as to why they intended useState
to be called separate times for each piece of state:
Separating independent state variables also has another benefit. It makes it easy to later extract some related logic into a custom Hook
In other words, if you wanted to abstract some of your re-usable code into a custom Hook, you might imagine that the hook has it's own state inside. What if this hook called useSomeCustomHook
was using two calls to useState
?
function MyComponent() {const [count, setCount] = useState(0)const value = useSomeCustomHook() // might call useState twice inside it// ...}
From React's perspective, they just when they call MyComponent()
that they see three calls to useState
. One for count
and two that are perceivably inside useSomeCustomHook()
.
Custom hooks were one of the main driving reason hooks were created in the first place. The fact that using state with function components isn't restricted to one object and one instantiation of all state like with classes is what allows us to have components with local state such as count
and them other local state from an custom Hooks.
Can I set state from state?
This type of question would only come from someone who had experience with class-based components. That's because it could be error-prone in classes to "set state from state" like this:
// In classes, this could be error prone:this.setState({ count: this.state.count + 1 })
For many reasons I won't get into here, you were supposed to do a different version of the state setting API when you wanted to set state from state in classes (to avoid bugs):
// The alternative API was to pass a function that// would receive the current state and return// new state:this.setState((currentState) => {return { count: currentState.count + 1 }})
Without knowing too much about classes, just know the types of "state batching" problems that we were avoiding by doing this alternative API wouldn't be a problem in the first place with function-based components. So setting "state from state" is fine with function based components that use useState
:
const [count, setCount] = useState(0)function add() {// setting state from statesetCount(count + 1)}
The state-setting function returned from useState
can still be passed a function which resembles the class-based way of doing it, but the nuances of why you might need this go beyond the scope of this article and just know that you probably won't need to do this often:
const [count, setCount] = useState(0)function add() {setCount((currentCount) => currentCount + 1)}
When do I use a reducer instead of useState
Here's the quick facts on useReducer
vs useState
:
- They both make local state
- You can use either one
Some will argue that useReducer
has some intangible benefits over useState
and I think they have some good points. I like using useReducer
for certain types of complex state. But just know that neither one has an aspect to its API that is impossible to solve with the other API. Here's what one of the owners of React Training had to say about it:
In other words, Michael is not saying he always uses an object to manage his state. He's just saying that when he has the type of state that most would consider a good use-case for useReducer
, he just uses an object with useState
instead -- the very thing that we just said to watch out for above.
But that's okay, he knows what he's doing!
We just want you to be aware that you have to manage the "merging" of your state on your own when it comes to function-components vs the older class ones.
How do I "share" my local state with other components?
Oh boy, this is kind of a huge topic. The short answer is you need to lift state.
Let's say I had a little tree-structure where the main App
was the owner component of the <Count />
component we've been making and also a new <Report />
component which reports what the current count is:
function App() {return (<div><Count /><Report /></div>)}
With the example above, the state for count
is inside the <Count />
component and is not being shared anywhere else.
To lift state, we would do this:
function App() {// This state has been moved "lifted" up one level:const [count, setCount] = useState(0)return (<div><Count count={count} setCount={setCount} /><Report count={count} /></div>)}
The <Count />
component might look more like this now:
function Counter({ count, setCount }) {// const [count, setCount] = useState(0)function add() {setCount(count + 1)}// ...}
It doesn't have it's own state, it has props instead. The add
function doesn't care where count
and setCount
come from. It just needs them to exist. When the button is clicked and add
calls setCount
and the new series of steps that takes place to update the UI is as follows:
- Since
setCount
came from auseState
inApp
, it's now the component that gets a re-render. - On the re-render, we get the latest state from
useState
and then pass it down as props to other components. - Components get re-rendered whenever they experience a state change or if their owner component gets re-rendered and therefore might be passing the child components new props.
- Therefore, both the
Report
andCount
components get re-rendered to return new descriptions of their UI.
These are all fundamental React concepts -- to lift state in order for two components to share it. From here though, it can turn into a much bigger conversation about how too much lifting state can lead to "prop drilling" and how prop drilling can be avoided with context and maybe you should be using Redux or MobX to handle global state and only use local state sometimes, etc, etc.
But that's all for another time 😎