DEV Community

Brett Bloxom
Brett Bloxom

Posted on • Updated on

React Hooks - useState

The useState hook allows us to make our function components stateful.

Create and Initialize State

When called, useState returns an array of two items. The first being our state value and the second being a function for setting or updating that value. The useState hook takes a single argument, the initial value for the associated piece of state, which can be of any Javascript data type.

We assign these two returned values to variables using array destructuring.

import React, { useState } from 'react';

const Component = () => {
    const [value, setValue] = useState(initial value)
    ...
Enter fullscreen mode Exit fullscreen mode

Since array elements have no names, we can name these two variables whatever we want. The general convention for declaring the name of your updater function is to begin with set and end with the name of your state variable, so [value, setValue]. The initial state argument passed in will be the value assigned to the state variable on the first render.

A Few Examples of State with Various Data Types

Each piece of state has its own call to useState and its own variable and function for setting/updating it.

const [count, setCount] = useState(0)
const [color, setColor] = useState('#526b2d')
const [isHidden, setIsHidden] = useState(true)
const [products, setProducts] = useState([])
const [user, setUser] = useState({
    username: '',
    avatar: '',
    email: '',
})
Enter fullscreen mode Exit fullscreen mode

Count is a number that we plan to increment or decrement, the initial value being 0. Color's initial value is a string holding the hash code with a default value of green. isHidden is a boolean with the initial value of true which we can assume describes the visibility of something in the DOM that will toggle between hidden and visible. Products' initial value is an empty array that we plan on populating with a list of products most likely fetched from an API. User is an object with several properties, all of which default to empty strings.

Initializing Expensive State

If your value is expensive to compute, like having to filter and manipulate a list of items, you can wrap the initialization in a function so that the useState will only call the function once rather than on every render.

const [filteredList, setFilteredList] = useState(() => listOf10MillionItems.filter())
Enter fullscreen mode Exit fullscreen mode

Updating Primitive Types

Updating state variables with useState always replaces the previous state. This means that updating primitive types (strings, booleans, numbers) is simple because their values are replaced rather than mutated.

Here is the classic and simple counter component example. We want to increment or decrement a number stored in state and display that number to the user or reset that number back to 0.

import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0)

  const increment = () => setCount(count + 1)
  const decrement = () => setCount(count - 1)
  const reset = () => setCount(0)

  return (
    <div className='counter'>
      <p className='count'>{count}</p>
      <div className='controls'>
        <button onClick={increment}>Increment</button>
        <button onClick={decrement}>Decrement</button>
        <button onClick={reset}>Reset</button>
      </div>
    </div>
  ) 
}

export default Counter
Enter fullscreen mode Exit fullscreen mode

Updating Arrays and Objects

When updating arrays or objects in state with useState, you must remember to pass the entire object or array to the updater function as the state is replaced, NOT merged as with the setState method found in class-based components.

Arrays

const [items, setItems] = useState([])

// Completely replaces whatever was stored in the items array
setItems([{item1}, {item2}])

// Don't use JS array methods such as pop, push, shift, unshift 
// as these will not tell React to trigger a re-render. 
items.push({item3})

// Instead, make a copy of the array then add your new item onto the end
setItems([...items, {item3}])

// To update an item in the array use .map. 
// Assumes each array item is an object with an id.
setItems(
  items.map((item, index) => {
    item.id === id ? newItem : item
  })
)
Enter fullscreen mode Exit fullscreen mode

Objects

const Person = () => {
  const [person, setPerson] = useState({
    firstName: '',
    lastName: ''
  });

  const handleChange = (e) => {
    setPerson({
      ...person,
      [e.target.name]: e.target.value
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault()
    // Form submission logic here.
  }

  return (
    <form>
      <label htmlFor='first'>
        First Name:
        <input
          id='first'
          name='firstName'
          type='text'
          value={person.firstName}
          onChange={handleChange}
        />
      </label>
      <label htmlFor='last'>
        Last Name:
        <input
          id='last'
          name='lastName'
          type='text'
          value={person.lastName}
          onChange={handleChange}
        />
      </label>
      <button type='submit' onClick={handleSubmit}>Submit</button>
    </form>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the above example, the handleChange function calls setPerson and passes in the person object from state using the spread operator with ...person. Without passing in the existing person object stored in state, the entire object would be overwritten anytime one of the input values changed.

Nested Objects and Arrays

To updated nested objects and arrays, each level needs to be copied and updated immutably as with the above examples.

const [people, setPeople] = useState({
  jerry: {
    firstName: 'Jerry',
    lastName: 'Garcia',
    address: {
      street: '710 Ashbury Street',
      city: 'San Francisco',
      state: 'CA',
      zip: '94117'
    }
  },
  jim: {
    firstName: 'Jim',
    lastName: 'Morrison',
    address: {
      street: '8021 Rothdell Trail',
      city: 'Los Angeles',
      state: 'CA',
      zip: '90046'
    }
  }
})

// Jerry is gonna move next door
setPeople({
  // Copy people
  ...people,
  // Overwrite person you want to update
  jerry: {
    // Copy Jerry's existing properties
    ...people.jerry,
    // Overwrite Jerry's address  
    address: {
      // Copy everything over from Jerry's original address
      ...people.jerry.address,
      // Update the street
      street: '712 Ashbury Street'
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Complex State

If you have complex state with multiple values, storing them in useState can become cumbersome. Another hook called useReducer is more suited to managing state with multiple values.

Thanks for reading!

Top comments (6)

Collapse
 
mayursarode4 profile image
mayursarode4

I need some help while using useState([]), with array of objects.

Collapse
 
brettblox profile image
Brett Bloxom

Hey @mayursarode4 ! Sorry I didn't see your comment. I'm sure you figured out your issue a long time ago, but I'm happy to help if you still need assistance.

Collapse
 
toreylittlefield profile image
Torey Littlefield • Edited

@brettblox thanks for the helpful write-up on useState and useReducer hooks. I needed to manage complex state for a lengthy form component so I went with useReducer which was much more manageable than having multiple states all over the place.

Collapse
 
devarmaghan profile image
Dev-Armaghan

hey fellows, I am having a bunch of confusion that is,
I am working in react native and the problem is that I want to update an array of objects using the useState but cant do it. Can anyone tell the way so that the whole object is updated. I have attach a picture

Collapse
 
brettblox profile image
Brett Bloxom

Yo! Sorry I didn't see your comment. I'm sure you figured out your issue a long time ago, but I'm happy to help if you still need assistance.

Collapse
 
pobx profile image
Pobx

Thank you so much. I feel confident to use it.