All the Hooks Series: useState
Tags: react, reacthooks
Published: 2020-05-06 22:17:14

---
title: All the Hooks Series: useState
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/is2h5357av9igwny7n5q.jpg
published: true
description: In this first post of the series, I will be going over the `useState` React hook. `useState` is one of the more straightforward and widely used hooks, so it makes a good foundation to learn when using React hooks.
tags: react, react hooks
series: All the Hooks Series
---

In this first post of the series, I will be going over the `useState` React hook. `useState` is one of the more straightforward and widely used hooks, so it makes a good foundation to learn when using React hooks.

## Getting Started with `useState`

`useState` is used for local state management in a React function component. To get started with the `useState` hook, you need a React function component and you need to import the `useState` hook from React.

```javascript
// import useState hook
import React, { useState } from "react";

// create function component
const Example = () => {
  // ...
}
```

Now we can declare a state variable. When using the `useState` hook, we declare the state variable in this format: `const [value, setValue] = useState(initialValue);`, where `value` is the name of the state variable (we get to name our state variables), `setValue` is a function that is used throughout the component to update the value of the state variable, and `initialValue` is the initial value of the state variable, if any. Note that `setValue` should be written in this format, with lowercase `set` followed by the variable, all in camelCase.

> Note: This format we are using with square brackets is called [`array destructuring`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring), where `useState` returns an array with two items and we turn those items into their own variables. 

If we want multiple state variables, we would follow this format multiple times, calling `useState` multiple times. For example:

```javascript
import React, { useState } from "react";

const Example = () => {
  const [count, setCount] = useState(0);
  const [whatToCount, setWhatToCount] = useState();
}
```

When reading state, we use curly braces in our JSX or use the variable in our JavaScript like any other variable in scope.

```javascript
import React, { useState } from "react";

const Example = () => {
  const [count, setCount] = useState(0);
  const [whatToCount, setWhatToCount] = useState("apples");

  return (
    <p>There are {count} {whatToCount}.</p>
  )
}
```

To update state, we use the updater function that was created, `setCount` (or `setWhatToCount`). This updater function may be called in a button event, for example, or from a function or other hook in our component.

```javascript
import React, { useState } from "react";

const Example = () => {
  const [count, setCount] = useState(0);
  const [whatToCount, setWhatToCount] = useState();

  return (
    <>
      <button onClick={() => setCount(count + 1)}>+</button>
      <p>There are {count} {whatToCount}.</p>
    </>
  )
}
```

It's important to note that the updater function doesn't update the value right away. If you need to do something with the updated value right away, you will need to use a variable that you set to the new value before you set the state, or you will need to move the work you need to do with the new value to a `useEffect` hook that runs when that piece of state changes. I don't want to go too much into `useEffect` for this post, but I will touch on it briefly.

Let's take this example:
```javascript
const Example = () => {
  const [count, setCount] = useState(0);

  const addCount = () => {
    setCount(count + 1);
    console.log('count', count); // 0
  }

  return (
    <button onClick={addCount}>+</button>
  )
}
```

In this example, the console will log `0` since it runs before `setCount` has completed. If we want to print the new count (`1`), we need to do the following:
```javascript
const Example = () => {
  const [count, setCount] = useState(0);

  const addCount = () => {
    const newCount = count + 1;
    setCount(newCount);
    console.log('count', newCount); // 1
  }

  return (
    <button onClick={addCount}>+</button>
  )
}
```

Or use `useEffect`:
```javascript
const Example = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('count', count); // 1, after count has changed
  }, [count]);

  const addCount = () => {
    setCount(count + 1);
  }

  return (
    <button onClick={addCount}>+</button>
  )
}
```

## Using `useState` with functional updater form

The `useState` hook is pretty straightforward, but there are cases you will run into as you do more advanced work where using something like `setCount(count + 1)` is not going to work well, and you will need to use the functional updater form of the updater function. I will review this more when we get to `useEffect` and `useCallback`, as that's really when the need to use this will come up, but I wanted to mention it here as well. This also comes up when working with async functions.

In some cases, using something like `setCount(count + 1)` will cause infinite re-rendering of your React app, causing it to crash. If you're using this in `useEffect`, for example, every time `count` changes the app may re-render. If `setCount` is running every time `count` changes, and `count` changes every time `setCount` runs, then you will get an infinite looping issue.

This is where the [functional updater form](https://reactjs.org/docs/hooks-reference.html#functional-updates) of `setState` comes in handy.

> If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.

So now, if we need to use the previous value of `count` when updating it, instead of using `setCount(count + 1)`, we would use: `setCount(prevCount => prevCount + 1)`. This way there is no dependency on `count`. Again, if you're not familiar with `useEffect` and `useCallback` yet, this will make more sense later in the series.

This functional updater form is also useful when performing async operations. Take the following for example:
```javascript
const Example = () => {
  const [count, setCount] = useState(0);

  const handleClickAsync = () => {
    setTimeout(function delay() {
      setCount(count + 1);
    }, 3000);
  }

  return (
    <div>
      {count}
      <button onClick={handleClickAsync}>+</button>
    </div>
  );
}
```

In this example, because of the delay introduced with `setTimeout`, the value of `count` will be stale by the time `setCount` runs, giving us unexpected outcomes. What we want is to use the most recent value of `count` rather than the value that `count` was when `setTimeout` was queued. Using the functional updater form, we can change the example to this:
```javascript
const Example = () => {
  const [count, setCount] = useState(0);

  const handleClickAsync = () => {
    setTimeout(function delay() {
      setCount(prevCount => prevCount + 1);
    }, 3000);
  }

  return (
    <div>
      {count}
      <button onClick={handleClickAsync}>+</button>
    </div>
  );
}
```

## Working with objects as state values

`useState` can hold any kind of value, including arrays and objects. However, because of the way JavaScript handles arrays and objects ([by value vs by reference](https://medium.com/dailyjs/back-to-roots-javascript-value-vs-reference-8fb69d587a18)), you may run into issues where you're trying to update an array or object state value and the component doesn't re-render and display the new state as expected. This becomes especially apparent when you're working with an array of objects.

The state updater doesn't merge new values with old values, it overwrites the state with the new value. React uses [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description) to decide if an object (or array of objects) is different and whether it should re-render. If you try to update an existing object, it is considered the same object, which means React won't re-render. You have to pass a brand new object to change the state.

There are a couple of ways you can update a state value that is an object and ensure that React recognizes the change and re-renders the component. One is to use `Object.assign` to create a new object and set the state to use this value. The other is to use the ES6 spread operator to create a new object with the values of the old object and any changes.

For example, the following won't trigger a re-render since the existing state object is being mutated and to React / JavaScript, it's the same object.
```javascript
const Example = () => {
  const [item, setItem] = useState({id: 1, value: ''});

  const editItem = () => {
    item.value = Math.random() * 100;
    setItem(item);
  }

  return (
    <button onClick={editItem}>Change the number</button>
  )
}
```

To make this work, a new object must be created.

Using `Object.assign` to create a new object:
```javascript
const Example = () => {
  const [item, setItem] = useState({id: 1, value: ''});

  const editItem = () => {
    const newItem = {
      id: prevValue.id,
      value: Math.random() * 100
    }
    const updatedItem = Object.assign({}, newItem);
    setItem(updatedItem);
  }

  return (
    <button onClick={editItem}>Change the number</button>
  )
}
```

Using the ES6 spread operator to create a new object:
```javascript
const Example = () => {
  const [item, setItem] = useState({id: 1, value: ''});

  const editItem = () => {
    setItem({
      ...item,
      value: value: Math.random() * 100
    })
  }

  return (
    <button onClick={editItem}>Change the number</button>
  )
}
```

## Working with arrays as state values

To add an item to a state variable that is an array, we want to create a new array and add the item, using the ES6 spread operator. We'll want to do something similar when changing existing items.

For example, don't try to push `Array.push` to add new items or directly modify the current array to change values in the array.

Instead, use the spread operator to create a new array using the value of the old array and add the items to the new array:
```javascript
const Example = () => {
  const [items, setItems] = useState([]);

  const addItem = () => {
    setItems([
      ...items,
      {
        id: items.length,
        value: Math.random() * 100
      }
    ]);
  }

  return (
    <button onClick={addItem}>Add a number</button>
  )
}
```

We can also do something like this if we want to change the value of an existing item in the array:
```javascript
const Example = () => {
  const [items, setItems] = useState([]);

  const editItem = (id) => {
    setItems(prevValue => {
      const updatedItems = prevValue; // create a new array using the previous value
     // modify our new array 
     updatedItems.map((item) => {
        if (item.id === id) {
          item.value = Math.random() * 100;
        }
        return item;
      }
      return [...updatedItems]; // return our new array with modified values using the spread operator
    }
  }

  return (
    <button onClick={() => editItem(3)}>Change a number</button>
  )
}
```

## Lazy initialization

From the [React docs](https://reactjs.org/docs/hooks-reference.html#lazy-initial-state):
> The `initialState` argument is the state used during the initial render. In subsequent renders, it is disregarded. If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render

What does this mean? Normally, a value (which could be returned from a function) can be passed to `useState` to set the initial state of the state variable:
```javascript
const [value, setValue] = useState(someFunction());
```

This initial state is used during the initial render but not in re-renders (rather whatever the state is set to using `setValue`, in this example). `useState` is called on every render, but the initial state *value* is disregarded after the initial render. `someFunction()` would still be called on subsequent renders, it's value is just going to be disregarded. If `someFunction()` is computationally expensive (maybe it deals with a large amount of data), having it called on re-renders would be bad for performance.

This is where we can use lazy initialization, so the function runs on the initial render only. We can do that by passing a function to `useState` that returns the result of the computationally expensive function. For example:

```javascript
const [value, setValue] = useState(() => someFunction());
```

## Updating state in an unmounted component (dealing with async updates)

Sometimes you'll be wanting to update your state after some async operation completes. However, if the component unmounts before the async operation is complete and the state update has had a chance to complete, you'll get a warning from React about updating state in an unmounted component.

> Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

Again, this is more of a topic for the `useEffect` and `useRef` hooks, but I wanted to briefly mention it here. Assuming you have currently cancelled other async operations, like API calls and timers, you can use `useRef` to create a variable that will tell you if the component is mounted, and only update your state if it is. For example:

```javascript
import React, { useState, useRef, useEffect } from "react";

const Example = () => {
  const _isMounted = useRef(true);
  const [apiRes, setApiRes] = useState();

  // Don't worry about this too much yet, but essentially _isMounted.current is set to `false` when this component unmounts
  useEffect(() => {
    return () => {
      _isMounted.current = false;
    }
  }

  // Again, don't worry about `useEffect` too much yet, but here we are performing some async operation and setting state after it's done. Using our `_isMounted` variable, we check if the component is mounted before we perform our state update.
  useEffect(() => {
    const someAsyncFunction = async () => {
      const res = await API.get("/api/some-api-endpoint");
      // Here is where we check that our component is still mounted before we make the state change
      if (_isMounted.current) {
        setApiRes(res.data);
      }
    }
  }, []);
}
```

## Conclusion

That's it for this first post in the All the Hooks Series! Feel free to leave a comment if anything is confusing to you or if you find an error or want to start a conversation about any of the topics covered. We can all learn from each other! 🤗 Stay tuned for the next post in the series where I will be covering `useEffect`.

## References
1. [Using the State Hook](https://reactjs.org/docs/hooks-state.html)
2. [4 Examples of the useState Hook](https://daveceddia.com/usestate-hook-examples/)
3. [A guide to useState in React](https://blog.logrocket.com/a-guide-to-usestate-in-react-ecb9952e406c/)
4. [The Wise Guide to React useState() Hook](https://dmitripavlutin.com/react-usestate-hook-guide/)
5. [Understanding React Hooks — useState](https://blog.bitsrc.io/understanding-react-hooks-usestate-6627120614ab)