CHAPTER 3useState
useState
makes React keep a
piece of state for us.
-
The first time it is called, we get to decide what is the initial value. React knows if it is the first time because the piece of state is just missing.
-
On the subsequent calls, React just returns the stored state.
Calling useState
useState
has 1 parameter which is the initial value (which is only really used
in the first call). Then it returns two things:
- the stored value, and
- a function to change it.
To be able to return two things, it returns an array with two cells. If we wrote the code normally, it would look like this:
const result = useState(0);
const value = result[0];
const setValue = result[1];
But since Javascript has destructuring, typically we will write it like this, which makes it easy to use and we get to decide what are the names given to the value and the function:
const [value, setValue] = useState(0);
A Counter
A classical first client component is a counter, which, using useState
, is
implemented like this:
export default function Counter() {
const [count, setCount] = useState(0);
const incr = () => setCount((c) => c + 1);
return (
<div>
<div>{count}</div>
<button onClick={incr}>Increment</button>
</div>
);
}
We have the call to useState
, creating a piece of state with an initial value
of 0. Then we define incr
, which is a function that will increment the state
by one unit (more on the setCount
call later). And finally the JSX is
returned, in which we have set the onClick
attribute of a button to call
incr
.
Changing the state
To change the state we have to call the function that was given to us by
useState
. This function can be called in two ways:
The function, though, is not like an assignment. A call such as:
setCount(value)
will "schedule" two things: a) a change in state, b) a re-render. Both this things will happen after the current task is done. So they are events put on the event queue after the current one (therefore, like an asynchronous function).
Passing a literal
Passing a literal like in
setCount(5);
will schedule a state change to 5. The same state change can be done a number of times:
setCount(5);
setCount(5);
and it will not change the result, since programming a change to 5 two times is the same as doing it once.
Passing an expression
A similar thing happens if we do:
const [count, setCount] = useState(0);
// ...
const incr = () => setCount(count + 1);
// ...
return <button onClick={incr} />
but this time, you can get confused by thinking that setCount(count + 1)
increments the counter like an assignment and think that, if we change incr
to:
const incr = () => {
setCount(count + 1);
setCount(count + 1);
}
the counter will be incremented by 2. That is not the case!
Reinterpreting, setCount(count + 1)
still says "program a change of state for later with a value of count + 1
". Since count + 1
will always be the same value, you can do as many changes as you want, and it will still increment by 1.
Passing a function
The solution for those cases is passing a function:
setCount(prev => prev + 1);
If setCount
sees a function, it will apply the function to whatever the value for the counter is at that time and it will update it, so something like:
setCount(prev => prev + 1);
setCount(x => x + 1);
will increment the counter by 2, since every time we are using the latest value of the count to compute the new one. Also, as you can see, the name of the parameter of the arrow function passed to setState
does not matter, albeit people usually call it prev
meaning "previous state".