React Hooks - useState
Tags: react, javascript, webdev
Published: 2020-04-05 01:45:50


> ***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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring). 

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

const Component = () => {
	const [value, setValue] = useState(initial value)
	...
```
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.

```javascript
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: '',
})
```
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. 

```javascript
const [filteredList, setFilteredList] = useState(() => listOf10MillionItems.filter())
```

## 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.
```javascript
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
```

## 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

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

###  Objects

```javascript
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>
  );
};
```

In the above example, the `handleChange` function calls `setPerson` and passes in the person object from state using [the spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) 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.

```javascript
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'
	}
  }
})
```

## Complex State

If you have complex state with multiple values, storing them in `useState` can become cumbersome. Another hook called [`useReducer`](https://dev.to/brettblox/react-hooks-usereducer-4g3m) is more suited to managing state with multiple values.  

Thanks for reading!