This blog post covers all you need to know about the concept of a state and react useState
hook from basics to advanced patterns. This blog assumes that you know about react fundamentals like Component, props, and jsx.
counter
is thehello world
of state management.
What is a state and why do we need state management in React? ⚛️
function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0)
// Action: code that causes an update to the state when something happens
const increment = () => {
setCounter(counter + 1)
}
// View: the UI definition
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
)
}
as you can see in this example there three main parts to Counter
component
- State is the truth that drives our application
- View is UI based on the state
- Action is an event that occurs in the application and changes the state
React uses the stateful values (which is generated by hook APIs like useState, useReducer) to know when to update the UI (View) part of an application. Whenever this state value changes React will update the component so that the state of the UI is the same as the state of the Component.
useState Hook 🎣
useState
is a function that accepts one argument as an initial value of any type for the state, and returns 2 elements in an array, first is the state value and the second is the updater function that we use to update the state value. This returned array is usually destructured so we can name the variables whatever we want but it is good practice and a common convention to prefix the set
in front of the updater function.
// you can pass any data-type
setState() // if you don't pass anything than value will be updated with undefined
setState('Thanks') // String
setState(4) // Number
setState(['reading']) // array
setState({ share : 💗 }) // object
setState(null) // null
In our Counter component
0
is the initial value for the state, and using destructuring we are naming our 2 variablescounter
andsetCounter
.setCounter
is used to update the counter value by 1 every time the button is clicked.
function Counter() {
const [counter, setCounter] = useState(0)
const increment = () => {
setCounter(counter + 1)
}
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
)
}
Lazy initialization of state 🦥
Every time React re-renders the component, useState(initialState)
is executed. if the initial state is some expensive function computation, e.g reading data from localStorage, mapping over some large amount of data, the instance with multiple methods ( e.g DraftJs
or ThreeJs
instance), then the component might face some performance issues.
// format : useState(() => initalState)
const [token, setToken] = useState(() => window.localStorage.getItem('token') || '')
we can use the lazy initalization to avoid the performance bottleneck for this all you need to do is put your initial state in function and that's it.
Update the state with callback 🤙
const [counter, setCounter] = useState(0);
const increment = () => {
setCounter(counter + 1);
setTimeout(() => {
setCounter(counter + 1);
}, 1000);
};
we have changed the increment function of the previous example, now we have added asynchronous behavior in our function what do you think the output will be?
Take a pause and think ,
Spoilers Ahead
You would see that after clicking the button once, even though we have 2 setCounter
calls, we still get a new count updated with 1 only.
So what is actually happening? 🤔
The problem is that the second call to the setCounter
is getting the same counter value as the first one. here in the example, both the setCounter
got the value of counter as 0
so they updated it to 1
.
But why 2nd the updater is getting the value of 0? 😕
For this, you need to understand how re-rendering actually works in React, We will not into the depths of this but in short re-rendering means if your state changes your whole component is replaced with the new one, In this example whole Counter
is called again and then it gets the new value. here we are using multiple consecutive updates and due to closure setCounter
has access to the counter
variable one we got from array destructuring which has a value of 0.
In the example we have the initial value as 0
when the button is clicked we update the value from 0 -> 1 but to get that updated state(1) react needs to re-render the component but here we are calling the setCounter
again to update counter with +1 and it gets counter as 0
so after one second when it updates the value 1
.
For most
async
activitiesuseEffect
would be a better choice. and for more complex statesuseReducer
.
Solution 🔥
When the new state is dependent on the previous state you can update the state with a callback.
const increment = () => {
setCounter(counter + 1);
setTimeout(() => {
// callback inside a updater function
setCounter(counter => counter + 1);
}, 1000);
};
If you would change the increment function with this new one you would have a reference to the internal state and not the closure value of state.
Use-cases 💼
// toggle a boolean
const [toggled, setToggled] = useState(false);
setToggled(toggled => !toggled);
// Update an object
const [size, setSize] = useState({ height : 500, width : 800})
setSize(currentSize => ({...currentSize , height : 700}))
// Update items in array
const [items, setItems] = useState([]);
setItems(items => [...items, 'push']);
Top comments (0)