🎯 What are Hooks?
- Hooks are a new feature addition in React version 16.8 which allows you to use React features without having to write a class.
- Previously you could use state only within class components but with hooks now it is possible to use state without writing a class.
- Hooks don't work inside classes.
🎯 Why Hooks?
- To work with classes you have to understand how this keyword works in javascript. You also need to remember to bind event handlers in class components.
- Classes don't minify very well and make hot reloading very unreliable.
- Hooks help to reuse stateful component logic without changing the component hierarchy.
- Rather than forcing a split based on the lifecycle methods, hooks let you split one component into smaller functions based on what pieces are related.
🎯 Rules of Hooks
- Only call hooks at the "Top Level" of your react functions
- Don't call hooks inside loops, conditions, or nested functions.
- Only call hooks from React functions. Call them from within React functional components and not just any regular javascript function.
Now let's learn about the very first hook- useState hook.
We will try to understand this using a simple example.
In our example, we'll create a simple button that will increment the value of the counter on the onClick
event.
🎯 Manage State using Class Component
First, we'll look at how to implement the same using the class component.
- Create a class component
- Create a
state
variable and initialized it to 0. - Create a method that is capable of setting this state value.
import React, { Component } from 'react';
class ClassCounter extends Component {
constructor(props){
super(props);
this.state = {
count: 0
}
}
handleIncrement = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<button onClick={this.handleIncrement}>
Increment Value
</button>
<p>{this.state.count}</p>
</div>
)
}
}
export default ClassCounter;
🎯 useState Hook
useState
is a hook that lets you maintain react state to functional components.- Hooks are just functions so we simply call them.
- This hook accepts an argument defining the initial value of the state property.
- It returns the pair of values.
current value
of the state property and amethod
that is capable of updating that state property.
📌 Manage State using Functional Component
Now let's take a look at how to implement the above example using a functional component.
- Create a functional component
- We need a
state
property initialized to 0. - We need a method capable of setting that state property value.
- Syntax which we have used below in
const [counter, setCounter] = useState(0);
is called Array Destructuring - The variable
counter
will always contain the current state value andsetCounter
will accept an argument and set a counter value to that argument.
import React, { useState } from 'react';
export const FunctionalCounter = () => {
const [counter, setCounter] = useState(0);
const handleIncrement = () => {
setCounter(counter + 1);
}
return (
<div>
<button onClick={handleIncrement}>
Increment Value
</button>
<p>{counter}</p>
</div>
)
}
Notice the usage of useState
hook in the above code snippet.
- A very first time the component renders a state variable is created and initialized with the default value of 0. The default value is never used on re-renders.
- When you click on the button the
setCounter
method is called which will add 1 to the current counter value. setCounter
method will cost the component to re-render.- After the re-render counter will contain the updated value.
📌 useState with previous state
Now let's take a look at an example where we'll see how to update the state based on the previous state.
Let's say, we want to implement the count value by 5 each time user clicks on the button.
Let's write a code for the same.
import React, { useState } from 'react';
export const FunctionalCounterWithPreviousState = () => {
const [counter, setCounter] = useState(0);
const handleIncrement = () => {
for(let i = 0; i < 5; i++){
setCounter(counter + 1);
}
}
return (
<div>
<button onClick={handleIncrement}>
Increment Value
</button>
<p>{counter}</p>
</div>
)
}
When we'll run the above code snippet you will notice that it will not work as expected. But Why??
Here comes the concept of Batch Rendering.
✏️ Batch Rendering
React may batch multiple setState()
calls into a single update for performance.
The main idea is that no matter how many setState calls you make inside a React event handler or synchronous lifecycle method, it will be batched into a single update. That is only one single re-render will eventually happen.
This functionality is relevant for both hooks and regular class components, and its purpose is to prevent unnecessary rendering.
As React updates the state Asynchronously, you should not rely on their values for calculating the next state.
So what other way we can use to achieve our target? Now we'll use the second form of the setCounter
function.
Basically, instead of passing in a value of the new state variable, we pass in the function that has access to the old state value.
So now we'll update our code as follows:
import React, { useState } from 'react';
export const FunctionalCounterWithPreviousState = () => {
const [counter, setCounter] = useState(0);
const handleIncrement = () => {
for(let i = 0; i < 5; i++){
// Update the value based on the previous value
setCounter(prevCounter => prevCounter + 1);
}
}
return (
<div>
<button onClick={handleIncrement}>Increment Value</button>
<p>{counter}</p>
</div>
)
}
Now it will work as expected.
📌 useState with Objects
Now we will use an object as a state variable with the useState hook. So instead of storing a variable, we will work with objects this time.
In the below example, we will initialize the state variable with an object with firstName & lastName as a key.
Let's take a look at the code first.
import React, { useState } from 'react';
const HooksWithObject = () => {
const [name, setName] = useState({firstName: '', lastName: ''});
return(
<div>
<div>
<label>Enter FirstName: </label>
<input
type='text'
value={name.firstName}
onChange={e => setName({firstName: e.target.value})}
/>
</div>
<div>
<label>Enter LastName: </label>
<input
type='text'
value={name.lastName}
onChange={e => setName({lastName: e.target.value})}
/>
</div>
<p>firstName: {name.firstName}</p>
<p>lastName: {name.lastName}</p>
</div>
)
}
export default HooksWithObject;
In the above code snippet, there are input fields that will update the values of firstName & lastName as a user types in the input fields.
As you notice above, whenever a user updates the lastName input field, the firstName input field gets reset to an empty string and removed from the state variable
This is happening because
useState
does not automatically merge and update the object.This is the key difference between the setState in Class Component and useState in the functional Component.
setState
will merge the state whereasuseState
hook setter function will not merge the state. You can use spread operator to handle this situation.
So now update our code accordingly.
import React, { useState } from 'react';
const HooksWithObject = () => {
const [name, setName] = useState({firstName: '', lastName: ''});
return(
<div>
<div>
<label>Enter FirstName: </label>
<input
type='text'
value={name.firstName}
onChange={e => setName({...name, firstName: e.target.value})}
/>
</div>
<div>
<label>Enter LastName: </label>
<input
type='text'
value={name.lastName}
onChange={e => setName({...name, lastName: e.target.value})}
/>
</div>
<p>firstName: {name.firstName}</p>
<p>lastName: {name.lastName}</p>
</div>
)
}
export default HooksWithObject;
In the above code snippet, while setting the name variable on onChange
event, we'll first copy every property in the name object and then just overwrite the firstName field with a different value.
Now when we'll check our result. It will work as expected.
🎯 Summary
- The useState hook allows you to maintain a state inside functional components.
- In classes, the state is always an object. With the useState hook, the state doesn't have to be an object. It can be an array, number, boolean or string, etc.
- The useState hook returns an array with 2 elements. The first element is the current value of the state and the second element is a state setter function.
- State setter function will cause the component to re-render.
- In case your new state value depends on the previous state value you can pass a function to the setter function. The setter function will receive the previous state as its argument.
- When dealing with objects or arrays, always make sure to spread your state variable and then call the setter function.
🎯 Wrap Up!!
That's all for this article. Thank you for your time!! Let's connect to learn and grow together.