DEV Community

Cover image for useState Hook In React
Anuradha Aggarwal
Anuradha Aggarwal

Posted on • Originally published at anuradha.hashnode.dev

useState Hook In React

🎯 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.

useState-class-component

🎯 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;

Enter fullscreen mode Exit fullscreen mode

🎯 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 a method 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 and setCounter 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>
  )
}

Enter fullscreen mode Exit fullscreen mode

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>
  )
}

Enter fullscreen mode Exit fullscreen mode

useState-class-component

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>
  )
}

Enter fullscreen mode Exit fullscreen mode

useState-batch-rendering.gif

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;

Enter fullscreen mode Exit fullscreen mode

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.

useState-with-object.gif

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 whereas useState 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;

Enter fullscreen mode Exit fullscreen mode

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.

useState-with-spread-object.gif

🎯 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.

LinkedIn Twitter Instagram

Buy-me-a-coffee

Top comments (0)