Table of Contents
Related Posts
Hello World 👋
Hooks are special types of functions in React that you can call inside React functional components. They let you store data, add interactivity, and perform some actions, otherwise known as side-effects.
Below are the most common hooks that you use:
- useState
- useEffect
- useRef
- useContext
- useReducer
In this article, we will learn in-depth about
useState
hook.useState
useState
is a built-in function in React. It takes a single argument and returns an array of two elements when executed.Let’s see an example.
const [count, setCount] = useState(0)
- It is used to manage the state of the component.
- When the state of a component changes, React re-renders that component and all of its child components automatically.
- It takes the initial value of the state as an argument.
- It returns an array of two elements.
- The first element is the value of the state.
- The second element is a function that you can use to set the value of the state
- You can name these elements whatever you like, but the common practice is to name them as
var
andsetVar
. For instance, in the above example, we named it ascount
andsetCount
.
In the above example, we called
useState
with the argument of 0. This means that the initial value of the state is 0. count
contains the value of the state. setCount
is a function you can use to set the value of count.Let’s see the complete example of a component to get an idea of how
useState
is used in React.import React, { useState } from "react"
function Counter() {
const [count, setCount] = useState(0)
function increment() {
setCount(count + 1)
}
return (
<button onClick={increment}>{count}</button>
)
}
This renders a simple button that shows the value of count. Initially, it is 0. Whenever you click on the button, the count value is increased by 1 by using
setCount
. And as soon as the state changes, the component rerenders and the new value of count is shown in the button.Functional Updates
Let’s slightly change the above component.
import React, { useState } from "react"
function Counter() {
const [count, setCount] = useState(0)
function incrementBy3() {
setCount(count + 2)
setCount(count + 1)
}
return (
<button onClick={incrementBy3}>{count}</button>
)
}
Now, when you click on the button, what would you expect the increment to be. Will the count be incremented by 2? (or) Will it be incremented by 1? (or) Will it be incremented by 3?
Click and try it out.
Here is the corresponding code sandbox for it.
You can observe that the count will only be incremented by 1. This is because of how React makes state updates. Many state updates are batched together and performed in an asynchronous manner. So, if you have two setState calls at the same place, you cannot rely on React to complete the first state update before doing the second state update.
Let’s take the above example. - Initially, the
count
is 0. - Button is clicked. - First, React starts executing setCount(count + 2)
. - The value of count
in that render is 0
. - React calculates the value of count
for the next render to be count + 2
, which is 2. - But the component is not yet re-rendered. So the current value of the variable count is still 0
. - Now, React starts executing setCount(count + 1)
. - Since the value of count
is still 0, React calculates the value of count
for the next render to be count + 1
which is 1. - The value of next state was 2 when setCount(count+2)
is executed. Now, it got overridden by the value of next state of setCount(count+1)
which is 1. - Now since all the state updates are executed, React starts rerendering the component with the value of the next state which is 1. - And this is the reason why, when you click on the button only 1 is getting incremented instead of 3.Even though it takes a while to understand the problem that is occurring, the solution to fix this problem is not that complicated.
Till now we have seen that
setCount
takes a value as an argument. But it also takes a callback as the argument of setCount
. The first argument of that callback function is the previous value of the state.For example, if we want to increment the count by 1, you can do it as follows:
setCount(previousCount => previousCount + 1)
When you use this type of callback function to update the state, you can be assured that the
prevCount
will always have the correct value of the state even though the component is not yet rerendered. That is why it is always recommended to use this type of update whenever the next value of the state is computed from the previous value of the state.Let’s use this approach and rewrite the above example.
import React, { useState } from "react"
function Counter() {
const [count, setCount] = useState(0)
function incrementBy3() {
setCount(previousCount => previousCount + 2)
setCount(previousCount => previousCount + 1)
}
return (
<button onClick={incrementBy3}>{count}</button>
)
}
This correctly increments the count by 3.
Lazy Initialization
const initialValue = resultOfSomeExpensiveOperation()
const [state, setState] = useState(initialValue)
Previously we have seen that the
useState
takes the initial value as an argument.Technically React only needs the initialValue when the component is first mounted. After that, the initialValue is no longer applicable. So if the initial value is calculated through some expensive operations, we want those operations to run only at the start. Let’s see if that is actually happening or not through an example.
import React, { useState } from "react";
function getInitialValue() {
console.log('getInitialValue is getting executed');
// ... do some expensive operations
return 0;
}
function Counter() {
const [count, setCount] = useState(getInitialValue());
function increment() {
setCount(count + 1);
}
return <button onClick={increment}>{count}</button>;
}
Try to click on the button and check the console. - You will see that the
getInitialValue
function is being executed every time the button is clicked. So, it is getting called on every rerender. - So, if there are any expensive operations to be made in that function, they will be executed after every rerender. - But only the first execution of the getInitialValue
is useful for React. All others will be thrown away as the state is already set in the subsequent rerenders. - You can see why this might cause performance problems for your application.React gives us a way to handle this type of situation. It’s called
Lazy Initialization of State
.Instead of directly passing the value as an argument, you have the option to pass a callback function which when executed gives you the initial value. React executes this function only when it is needed. This is needed only at the first, so React executes this function only once at the start.
Let’s rewrite the above example to use lazy state initialization.
import React, { useState } from "react";
function getInitialValue() {
console.log('getInitialValue is getting executed');
// ... do some expensive operations
return 0;
}
function Counter() {
const [count, setCount] = useState(() => getInitialValue());
function increment() {
setCount(count + 1);
}
return <button onClick={increment}>{count}</button>;
}
All we changed in the above example is that:
useState(getInitialValue())
is changed to useState(() => getInitialValue())
.%[https://codesandbox.io/s/simple-counter-5-2rois]
You can check the console of the above code sandbox. You will see that
getInitialValue
is not getting called when you click on the button. It is only called at the start.useState with Objects
You can manage any type of state with
useState
hook, even objects.For example, let’s use useState hook to manage firstName and lastName states in a single object.
const [name, setName] = useState({
firstName: 'Bhanu Teja',
lastName: 'P'
})
Now, whenever you call
setName
to update the name object, you have to provide both firstName and lastName.For example,
setName({
firstName: 'New First Name',
lastName: 'New Last Name'
})
What if we want to update only the firstName or only the lastName. You can do so by using the spread operator.
function setFirstName(firstName) {
setName({
...name,
firstName
})
}
function setLastName(lastName) {
setName({
...name,
lastName
})
}
Let’s put everything together.
import React, { useState } from "react";
function App() {
const [name, setName] = useState({
firstName: "Bhanu Teja",
lastName: "P"
});
function setFirstName(firstName) {
setName({
...name,
firstName
});
}
function setLastName(lastName) {
setName({
...name,
lastName
});
}
return (
<>
<input
placeholder="First Name"
type="text"
value={name.firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input
placeholder="Last Name"
type="text"
value={name.lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<p>
Your name is:{" "}
<strong>
{name.firstName}.{name.lastName}
</strong>
</p>
</>
);
}
Usage of
useState
with arrays is very similar to that of with objects.What have you learned?
You learned about: - What are hooks and what are some of the common hooks in React ? -
useState
hook: - It takes the initial value of the state as an argument and returns an array of two elements - one has the value of the state and the other is a function to update the value of the state. - How to do functional updates for the state? - Why, not using functional updates, causes a problem in certain situations? - It is always recommended to use functional updates to update the state when the next state is computed from the previous state. - Lazy initialization of state and when it can be useful. - Usage of useState
with objects and arrays.What’s Next?
In the next article, we will learn everything about
useEffect
hook.Until Next Time 👋
Written by
24yo developer and blogger. I quit my software dev job to make it as an independent maker. I write about bootsrapping Feather.