React Hooks - useReducer
Tags: react, javascript, webdev
Published: 2020-04-19 19:12:32

> ***`useReducer` is usually preferable to  [`useState`](https://dev.to/brettblox/react-hooks-usestate-43en), when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.***

## Initialization
Similar to [`useState`](https://dev.to/brettblox/react-hooks-usestate-43en), when called, `useReducer` returns an array of two items. The first being our current state and the second being a dispatch method. 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
const [state, dispatch] = useReducer(reducer, initialState);
```
`useReducer` takes two arguments and (and an **optional** 3rd which we will cover later). The first argument is a reducer function, and the second is our initial state value, similar to  [`useState`](https://dev.to/brettblox/react-hooks-usestate-43en).

## What is a Reducer?
Reducer functions are not specific to React. They are simply Javascript functions that take in two arguments: an initial value, and instructions for what to do to that value. The reducer applies some sort of logic to the value based on the instructions that you provided and returns an entirely new value.
```javascript
const reducer = (value, instructions) => newValue
```
An important thing to understand about reducers is that **they will always only return one value**. Reducers are  [***pure functions***](https://en.wikipedia.org/wiki/Pure_function) that _reduce_ the original input into a single return value without mutating the original value that was passed in and, given the same arguments, will always produce the same return value. 

A good example of this pattern in Javascript is the `.reduce()` array method. As with `useReducer`, this method takes two arguments: a reducer function and the initial value from which to apply the reducer function against. 

```javascript
const nums = [1, 2, 3]  
const initialValue = 0  

const reducer = (accumulator, item) => accumulator + item 
const total = nums.reduce(reducer, initialValue)

console.log(nums) // [1, 2, 3]
console.log(total) // 6
```
In this example, `.reduce()` loops through our `nums` array, and applies our `reducer` function for each iteration. Our `initialValue` is what we want the reducer to use as its starting point on the first iteration. The `accumulator` is the collected value returned in the last invocation that informs the function what the next value will be added to.  

**1st iteration:** `0 + 1 => 1`
**2nd iteration:** `1 + 2 => 3`
**3rd iteration:** `3 + 3 => 6`

The `nums` array was reduced into the single return value of 6.

## How are Reducers Used in React?
In React, reducers are responsible for handling transitions from one state to the next state in your application. The initial value we provide to the reducer is our current state and the instructions we provide are called actions. 

The current state and the action go in, the new state comes out the other side. 
```javascript
const reducer = (state, action) => newState
```
Reducer functions handle state transitions by determining what to do based on information provided by the action.

## Actions 
Actions express _unique events_ that happen throughout your application. From user interaction with the page, external interaction through network requests, and direct interaction with device APIs, these and more events can be described with actions.

Here are some ***general conventions*** for actions described by the [Flux standard for action objects](https://github.com/redux-utilities/flux-standard-action):

An action **MUST**
* be a plain JavaScript object;
* have a `type` property

An action **MAY**
-   have an  `error`  property.
-   have a  `payload`  property.
-   have a  `meta`  property.

An action **MUST NOT** include properties other than `type`, `payload`, `error`, and `meta`.

### `action.type`

The  `type`  of an action identifies to the consumer the nature of the action that has occurred.  `type`  is a string constant. If two types are the same, they **MUST** be strictly equivalent (using  `===`).

```javascript
// Action with type property
{
  type: 'ADD_TODO'
}
```

### `action.payload`

The **optional**  `payload`  property **MAY** be any type of value. It represents the payload of the action. Any information about the action that is not the  `type`  or status of the action should be part of the  `payload`  field.

```javascript
// Action with type and payload properties
{
  type: 'ADD_TODO',
  payload: {
    todo,
    completed: false,
    id: id()  
  },
}
```

### `action.error`

The **optional**  `error`  property **MAY** be set to  `true`  if the action represents an error.

An action whose  `error`  is true is analogous to a rejected Promise. By convention, if  `error`  is  `true`, the  `payload`  **SHOULD** be an error object. This is akin to rejecting a promise with an error object.

```javascript
// Action representing an error. The error property is set to true, therefore the payload is an error object.
{
  type: 'ADD_TODO',
  payload: new Error(),
  error: true
}
```

### `action.meta`

The **optional**  `meta`  property **MAY** be any type of value. It is intended for any extra information that is not part of the payload.

## Dispatching Actions
As I mentioned at the beginning, when initialized, `useReducer` returns an array of two items. The first being our current state and the second being a dispatch method. 
```javascript
const [todos, dispatch] = useReducer(reducer, [])
```
When invoked, this dispatch method is responsible for passing an action to our reducer function.

Actions are dispatched when specific events take place. In following with the todo app example used thus far, these events could be represented by actions such as:

* Adding a todo
* Deleting a todo
* Toggling whether a todo item is completed or not.

Let's create some action types for these events.

```javascript
const ADD_TODO = 'ADD_TODO'
const DELETE_TODO = 'DELETE_TODO'
const TOGGLE_COMPLETED = 'TOGGLE_COMPLETED'
```

We could use strings throughout our application when using these action types, but by assigning them to variables, we avoid the issue of misspelling the string, which would not throw an error, leading to wasted time spent tracking down the bug. If we misspell the variable name, we will get a useful error message telling us what we did wrong.

Now lets add some handler functions that will call dispatch, passing it an action object. These handlers will be triggered when certain events take place. 
```javascript
// calls dispatch, passing it an action object with a type property of ADD_TODO, 
// and a payload property containing the todo text that was passed in,
// a default value of false for the completed property, and a unique id.
const addTodo = todo => {
  dispatch({
    type: ADD_TODO,
    payload: {
      todo,
      completed: false,
      id: id()
    }
  });
};

// calls dispatch, passing it an action object with a type property of DELETE_TODO,
// and accepts an id which is the only property in our payload. 
const deleteTodo = id => {
  dispatch({
    type: DELETE_TODO,
    payload: {
      id
    }
  });
};

// calls dispatch, passing it an action object with a type property of TOGGLE_COMPLETED,
// and accepts an id which is the only property in our payload. 
const completeTodo = id => {
  dispatch({
    type: TOGGLE_COMPLETED,
    payload: {
      id
    }
  });
};
```

Each action, when dispatched, will be handled differently by our reducer. A common pattern you will see with reducers is the use of switch statements. This isn't a requirement and any conditional logic will do so long as we are optimizing for readability. For the sake of showing something other than a switch statement, here is what a reducer for handling our todo app might look like with an if-else statement.

```javascript
const todoReducer = (state, action) => {
  if (action.type === ADD_TODO) {
    return [action.payload, ...state]
  }
  if (action.type === DELETE_TODO) {
    return state.filter(todo => todo.id !== action.payload.id)
  }
  if (action.type === TOGGLE_COMPLETED) {
    return state.map(todo => {
	  if (todo.id !== action.payload.id) return todo
	  return {...todo, completed: !todo.completed}
	})
  }
  return state
}
```
The above reducer knows what to do when given each type of action.

If the dispatched action has a type property of **ADD_TODO**:
* Return a copy of the current state, adding the new todo to the beginning of the array.

If the dispatched action has a type property of **DELETE_TODO**:
* Filter our list of todos, returning a new list of all todos whose id does not match the id passed with our action's payload, therefore removing the todo item from the list.

If the dispatched action has a type property of **TOGGLE_COMPLETED**:
* Loop through our list of todos, looking for the todo whose id property matches the id from the action's payload. If they do not match, return the todo item as is. If a match is found,  copy the todo item's properties,  replacing the `completed` property with the opposite of what it was. 

If none of those are true and we receive an unrecognized action, return the current state as is. 

## Putting It All Together
We have covered the basic components of how to use the reducer hook for managing more complex state. Let's look at a more practical example of using `useReducer` for managing state in a typical contact form component.

Let's start by building out the very basic structure of our form component.

```javascript
import React, { useReducer } from  'react'

const Form = () => {

  // for now, we will just prevent the default 
  // behaviour upon submission
  handleSubmit = e => {
    e.preventDefault()
  }
  
  return (
    <>
      <h1>Send a Message</h1>
      <form onSubmit={handleSubmit}>
        <label htmlFor='name'>
          Name
          <input id='name' name='name' type='text' />
        </label>
        <label htmlFor='email'>
          Email
          <input id='email' name='email' type='email' />
        </label>
        <label htmlFor='subject'>
          Subject
          <input id='subject' name='subject' type='text' />
        </label>
        <label htmlFor='body'>
          Body
          <textarea id='body' name='body' />
        </label>
        <button type='submit'>
          Send
        </button>
      </form>
    </>
  )
}

export default Form
```

Next, let's declare our action types, an object representing our initial state, and our reducer function. You can declare these inside of your component or out, or write them in a separate file and import them where needed. For this example, I will be declaring them in the same file, but outside of our component to keep our `<Form />` a bit less cluttered and easier to read. 

We also need to initialize our `useReducer` hook, passing it our newly created reducer function and initial state object.

For variety, I will use a switch statement in our reducer. 

```javascript
import React, { useReducer } from  'react'

// action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'

// initial state
const INITIAL_STATE = {
  name: '',
  email: '',
  subject: '',
  body: '',
}

// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }
    default: 
      return INITIAL_STATE
}

// form component
const Form = () => {
  // initialize useReducer
  const [state, dispatch] = useReducer(formReducer, INITIAL_STATE)
  ...
```

Now we need to give control of our inputs over to [React](https://reactjs.org/docs/forms.htm) so that we can store the input values in state. 

First, let's set the value of each input to the respective value stored in state.

```javascript
<input 
  id='name'
  name='name' 
  type='text' 
  value={state.name} 
/>
```

Doing this alone will disable our input because we have hardcoded the value to an empty string with no instructions for how to handle the change event.

So, we also need to provide an `onChange` attribute to our input and pass to it a function so that we can update the values stored in state.

```javascript
<input 
  id='name' 
  name='name' 
  type='text' 
  value={state.name} 
  onChange={e => updateFieldValue(e.target.name, e.target.value)}
/>
```

And our `updateFieldValue` handler function: 

```javascript
const updateFieldValue = (field, value) => {
  dispatch({
    type: UPDATE_FIELD_VALUE,
    payload: {
      field,
      value,
    },
  })
}
```

Now when a user types in our input field, the `updateFieldValue` function is triggered, which dispatches an action to our `formReducer` with a type of `UPDATE_FIELD_VALUE`, and a payload which includes the field that was updated, and the new value of that field.  

Our `formReducer` knows what to do with this action type and returns a new state with the updated field values. 

Here is what our Form component looks like thus far:
```javascript
import React, { useReducer } from  'react'

// initial state values
const INITIAL_STATE = {
  name: '',
  email: '',
  subject: '',
  body: '',
}

// action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'

// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case  UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }
    default:
      return INITIAL_STATE
  }
} 

// Form component
const Form = () => {
  const [state,  dispatch] = useReducer(formReducer, INITIAL_STATE)
  
  // input change handler function
  const updateFieldValue = (field, value) => {
    dispatch({
      type: UPDATE_FIELD_VALUE,
      payload: {
        field,
        value,
      },
    })
  } 
  
  // submit handler
  const handleSubmit = event => {
    event.preventDefault()
  } 
  
  return (
    <>
      <h1>Send a Message</h1>
      <form onSubmit={handleSubmit}>
        <label htmlFor='name'>
          Name
          <input
            id='name'
            name='name'
            type='text'
            value={state.name}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='email'>
          Email
          <input
            id='email'
            name='email'
            type='email'
            value={state.email}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='subject'>
          Subject
          <input
            id='subject'
            name='subject'
            type='text'
            value={state.subject}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
          />
        </label>
        <label htmlFor='body'>
          Body
          <textarea
            id='body'
            name='body'
            type='text'
            value={state.body}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <button type='submit'>
          Send
        </button>
      </form>
    </>
  )
}

export  default  Form
```
Our form is successfully using the reducer hook to update and keep track of our input values in state. Now we need to handle the various states associated with submitting the form and display those states to the user.

## Adding Form States
At this point, we only have one type of action for updating the values of our various input fields in state. This alone is a valid use case for `useReducer`, but when thinking about all of the states involved with submitting a form, updating and storing the input values is only one small piece of the equation. 

**Here are a few of the common states that our form could be in:**
* **Idle**: Our initial state. An empty form, ready to be filled out and submitted;
* **Pending**: We submitted the form and are waiting to find out if the submission was successful or not;
* **Success**: Our form was submitted successfully;
* **Error**: Something went wrong while trying to send the form;

All of these form states need to be tracked and communicated to the user. Each status will be represented by a different UI. 

Let's add a new action type for representing these state changes:

```javascript
// action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'
const UPDATE_STATUS = 'UPDATE_STATUS'
```
Similar to our action types, I'm going to declare a few new variables for our current form states to avoid the issue I mentioned earlier with using strings instead of variables. We want useful error messages if we end up making a spelling mistake.
```javascript
// form status variables
const IDLE = 'UPDATE_FIELD_VALUE'
const PENDING = 'PENDING'
const SUCCESS = 'SUCCESS'
const ERROR = 'ERROR'
```
Also add a new `status` property to our initial state with default value of `IDLE`
```javascript
// initial state
const INITIAL_STATE = {
  name: '',
  email: '',
  subject: '',
  body: '',
  status: IDLE,
}
```
We now need to add a new `case` for dealing with an action type of `UPDATE_STATUS`. If an action is dispatched with a type of `UPDATE_STATUS`, we return a copy of the state as is, replacing the value of our `status` property with the new value from our actions's payload. 
```javascript
// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }  
    case UPDATE_STATUS:
      return { ...state, status: action.payload.status }
    default: 
      return INITIAL_STATE
}
```
Inside of our `Form` component, let's add a new handler function for communicating that an `UPDATE_STATUS` event has occurred. We will call this handler `updateStatus`. 
```javascript
// Form component
const Form = () => {
  const [state,  dispatch] = useReducer(formReducer, INITIAL_STATE)
  
  // handler functions
  const updateFieldValue = (field, value) => {
    dispatch({
      type: UPDATE_FIELD_VALUE,
      payload: {
        field,
        value,
      },
    })
  } 

  const updateStatus = status => {
    dispatch({
      type: UPDATE_STATUS,
      payload: {
        status,
      },
    })
  }
  ...
```
We can now give our `handleSubmit` function the logic for updating the `status` property in state. Typically, you would send a `POST` request to some sort of API responsible for handling incoming messages in a [`useEffect`](https://dev.to/brettblox/react-hooks-useeffect-36bk) hook. This API would then communicate whether or not this was successful by providing an error response or a success response. For now, we will mock this functionality by initially setting our `status` to `PENDING`, then after two seconds, setting its value to `SUCCESS`. 

```javascript
  ...
  // submit handler
  const handleSubmit = event => {
    event.preventDefault()
    updateStatus(PENDING) 
    
    setTimeout(()  =>  {
      updateStatus(SUCCESS)
    },  2000)
  } 
  ...
```

Now in our form, we can add some markup for displaying `IDLE`, `PENDING`, `SUCCESS`, and `ERROR` states to the user.

```javascript
...
  // Success state
  if (state.status === SUCCESS) {
    return <p>Your message was sent successfully.</p>   
  }  

  // Error state
  if  (state.status === ERROR) {
    return <p>Oops! Something went wrong...</p>
  } 

  // Default State
  return (
    <>
      <h1>Send a Message</h1>
      <form onSubmit={handleSubmit}>
        <label htmlFor='name'>
          Name
          <input
            id='name'
            name='name'
            type='text'
            value={state.name}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='email'>
          Email
          <input
            id='email'
            name='email'
            type='email'
            value={state.email}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='subject'>
          Subject
          <input
            id='subject'
            name='subject'
            type='text'
            value={state.subject}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
          />
        </label>
        <label htmlFor='body'>
          Body
          <textarea
            id='body'
            name='body'
            type='text'
            value={state.body}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <button type='submit' disabled={state.status === PENDING}>
          {state.status !== PENDING ? 'Send' : 'Sending...'}
        </button>
      </form>
    </>
  )
}

export  default  Form
```
With this in place, upon submission of our form, the `status` is set to `PENDING` for two seconds, which disables the submit button and changes the button text to ***Sending...*** instead of ***Send***. 

After two seconds, the `status` is set to `SUCCESS` which renders the message ***Your message was sent successfully.*** instead of our form. 

To see the `ERROR` message right now, you can hardcode the `status` to `ERROR` in the `INITIAL_STATE`, which will display the message ***Oops! Something went wrong...*** instead of our form.

At this point, we have the base functionality in place for managing state in most forms. You will still need to swap out our submit handler with real functionality and also write your styles for helping communicate the various form states. 

The only missing piece is a reset button for allowing the user to send another message upon a successful or unsuccessful submit attempt. For this, we will utilize the optional third parameter to `useReducer` that I mentioned at the beginning of this article.

## Lazy Initialization
`useReducer` also gives us the ability to create the initial state lazily. To do this, you can pass an  `init`  function as the **optional** third argument. 

The initial state will be set to  `init(initialState)`.

```javascript
const [todos, dispatch] = useReducer(reducer, initialState, init);
```

The `init` function lets you extract the logic for calculating the initial state outside of the reducer. This is also handy for resetting the state to its initial values in response to an action. 

In our case, this action will have a type of `RESET`, so lets add another action type for this:
```javascript
//action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'
const UPDATE_STATUS = 'UPDATE_STATUS'
const RESET = 'RESET'
``` 

Declare our init function:
```javascript
// init function passed as optional 3rd argument for lazy initialization
const init = initialState => initialState
``` 

Add a new case for for handling the new action type
```javascript
// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case  UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }
    case UPDATE_STATUS:
      return { ...state, status: action.payload.status }
    case RESET:
      return init(INITIAL_STATE)
    default:
      return INITIAL_STATE
  }
} 
```

Pass our init function as the third argument to `useReducer`:
```javascript
// Form component
...
const Form = () => {
  const [state,  dispatch] = useReducer(formReducer, INITIAL_STATE, init)
...  
```

Add a new handler function:
```javascript
...
const resetForm = () => {
  dispatch({ type: RESET })
}
...
```

And lastly, update our `SUCCESS` and `ERROR` UI's to include a button that triggers our `resetForm` handler function, setting the form back to its original state and displaying that to the user.

```javascript
...
// Success state
if (state.status === SUCCESS)  {
  return (
    <>
      <p>Your message was sent successfully.</p>
      <button type='button' onClick={resetForm}>
        Send Another Message
      </button>
    </>
  )
}  

// Error state
if (state.status === ERROR)  {
  return (
    <>
      <p>Something went wrong...</p>
      <button type='button' onClick={resetForm}>
        Try Again
      </button>
    </>
  )
}
... 
```
## Our Finished Form Component
```javascript
import React, { useReducer } from 'react'

// form status variables
const IDLE = 'UPDATE_FIELD_VALUE'
const PENDING = 'PENDING'
const SUCCESS = 'SUCCESS'
const ERROR = 'ERROR'

// initial state values
const INITIAL_STATE = {
  name: '',
  email: '',
  subject: '',
  body: '',
  status: IDLE,
}

// action types
const UPDATE_FIELD_VALUE = 'UPDATE_FIELD_VALUE'
const UPDATE_STATUS = 'UPDATE_STATUS'
const RESET = 'RESET'

// 3rd parameter for lazy initialization
const init = initialState => initialState

// reducer function
const formReducer = (state, action) => {
  switch (action.type) {
    case  UPDATE_FIELD_VALUE:
      return { ...state, [action.payload.field]: action.payload.value }
    case UPDATE_STATUS:
      return { ...state, status: action.payload.status }
    case RESET:
      return init(INITIAL_STATE)
    default:
      return INITIAL_STATE
  }
} 

// Form component
const Form = () => {
  const [state, dispatch] = useReducer(formReducer, INITIAL_STATE, init)
  
  // handler functions
  const updateFieldValue = (field, value) => {
    dispatch({
      type: UPDATE_FIELD_VALUE,
      payload: {
        field,
        value,
      },
    })
  } 

  const updateStatus = status => {
    dispatch({
      type: UPDATE_STATUS,
      payload: {
        status,
      },
    })
  }
  
  const resetForm = () => {
    dispatch({ type: RESET })
  }
  
  // MOCK submit handler
  const handleSubmit = event => {
    event.preventDefault()
    updateStatus(PENDING) 
    
    setTimeout(()  =>  {
      updateStatus(SUCCESS)
    },  2000)
  } 

  // Success state UI
  if (state.status === SUCCESS)  {
    return (
      <>
        <p>Your message was sent successfully.</p>
        <button type='button' onClick={resetForm}>
          Send Another Message
        </button>
      </>
    )
  }  

  // Error state UI
  if (state.status === ERROR)  {
    return (
      <>
        <p>Something went wrong...</p>
        <button type='button' onClick={resetForm}>
          Try Again
        </button>
      </>
    )
  } 

  // Default state UI
  return (
    <>
      <h1>Send a Message</h1>
      <form onSubmit={handleSubmit}>
        <label htmlFor='name'>
          Name
          <input
            id='name'
            name='name'
            type='text'
            value={state.name}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='email'>
          Email
          <input
            id='email'
            name='email'
            type='email'
            value={state.email}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <label htmlFor='subject'>
          Subject
          <input
            id='subject'
            name='subject'
            type='text'
            value={state.subject}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
          />
        </label>
        <label htmlFor='body'>
          Body
          <textarea
            id='body'
            name='body'
            type='text'
            value={state.body}
            onChange={e => updateFieldValue(e.target.name, e.target.value)}
            required
          />
        </label>
        <button type='submit' disabled={state.status === PENDING}>
          {state.status !== PENDING ? 'Send' : 'Sending...'}
        </button>
      </form>
    </>
  )
}

export  default  Form
```

## Recap
* **`useReducer` is preferable to  [`useState`](https://dev.to/brettblox/react-hooks-usestate-43en) when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one;**
* **When called, `useReducer` returns an array of two items: the current state, and a dispatch method;**
* **`useReducer` accepts three arguments: A reducer function, the initial state, and the optional init function for lazy initialization of state;**
* **In React, reducers are responsible for handling transitions from one state to the next state in your application. Reducers take in the current state and an action and return an entirely new state;**
* **Actions express _unique events_ that happen throughout your application.**
* **A few _general conventions_ for actions have been described by the [Flux standard for action objects](https://github.com/redux-utilities/flux-standard-action);**
*  **Actions are dispatched to our reducer when specific events take place;**

Thanks for reading!