# useReducer Hook

## Similarity between `useState` and `useReducer`

Both hooks have 3 things in common:&#x20;

1. State variable
2. Function to change the state
3. Initial value for the state

<figure><img src="https://109962327-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MC0Wq3Hv_Fc0WRkNF0M%2Fuploads%2FTgZyomdPdbarVW3hARjp%2FScreenshot%20(486).png?alt=media&#x26;token=ae593cd3-7e65-4ace-a2ad-20124c305966" alt=""><figcaption><p>Similarity between <code>useState</code> and <code>useReducer</code></p></figcaption></figure>

## Difference between `useState` and `useReducer`

With `useState` we define each state in a seperate variable. With `useReducer` we define all the states in a single object.

<figure><img src="https://109962327-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MC0Wq3Hv_Fc0WRkNF0M%2Fuploads%2FWbA1SlNrfs6xpLxnYRqV%2FScreenshot%20(487).png?alt=media&#x26;token=8f1821c2-bae7-431b-8c25-b722a15e74ef" alt=""><figcaption></figcaption></figure>

## `useReducer` Hook Workflow

`useReducer` Hook Workflow:

* We call `dispatch()` to anytime we want to update the state.
* Everytime `dispatch()` is called it calls the `reducer()` function.
* In the `reducer()` function the `state` parameter refers to the current state and `action` parameter refers to the **first argument** we pass to the `dispatch()` function.
* `dispatch()` typically pass two properties - `type` and `payload` to the `action` object.
* We pass `type` property to the `action` object to indicate which state needs to be updated and `payolad` property to the `action` object to send some data.
* We pass a `payload` property to the&#x20;
* The `reducer()` function returns a brand new state and our component re-renders.

<figure><img src="https://109962327-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MC0Wq3Hv_Fc0WRkNF0M%2Fuploads%2FKGNTaW9uNwpw4WwDTAki%2FScreenshot%20(488).png?alt=media&#x26;token=dd8ee7be-d087-4b68-acdf-a605029aef54" alt=""><figcaption><p><code>useReducer</code> Workflow</p></figcaption></figure>

## `reducer()` Rules

Some rules:

* Don't use async/await, request operations within `reducer()`
* Don't modify state directly.

<figure><img src="https://109962327-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MC0Wq3Hv_Fc0WRkNF0M%2Fuploads%2FQkLHFWpK1MZm9FQ02TKc%2FScreenshot%20(490).png?alt=media&#x26;token=b03dd1df-b840-4a6d-ab7e-f6718aa89b50" alt=""><figcaption></figcaption></figure>

<figure><img src="https://109962327-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MC0Wq3Hv_Fc0WRkNF0M%2Fuploads%2FS5HbphfQ0iFr2KqyPDxS%2FScreenshot%20(496).png?alt=media&#x26;token=4c6498f4-0126-486b-8871-5ab9c4e4dbb5" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
It's good to put logic within `reducer()` function
{% endhint %}

## `useReducer` Hook Example

Simple app using `useReducer`hook.

{% tabs %}
{% tab title="App.js" %}

```jsx
import React, {useState, useReducer} from "react"

const ACTIONS = {
    INCREMENT : "increment",
    DECREMENT : "decrement"
}

// `reducer` function takes 2 parameters - `Current State` and `action`. It returns a `New State`.
function reducer(state, action){
    switch(action.type){
        case ACTIONS.INCREMENT:
            return {count: state.count + 1}
        case ACTIONS.DECREMENT:
            return {count: state.count - 1}
        default:
            return state
    }
}

function App() {
    
    // useReducer hook accepts 2 parameters - a `reducer function` that performs action on State and a `value`(as object) that represents `Initial State` 
    const [state, dispatch] = useReducer(reducer, { count:0 }) // dispatch calls `reducer` function
    
    function increment() {
        dispatch({type: ACTIONS.INCREMENT})
    }
    
    function decrement() {
        dispatch({type: ACTIONS.DECREMENT})
    }
    
    return (
        <div>
            <h1>{state.count}</h1>
            <button onClick={increment}>Increment</button>
            <button onClick={decrement}>Decrement</button>
        </div>
    )
}

export default App

```

{% endtab %}

{% tab title="index.js" %}

```jsx
import React from "react"
import ReactDOM from "react-dom"

import App from "./App"

ReactDOM.render(<App />, document.getElementById("root"))

```

{% endtab %}
{% endtabs %}

## Managing Complex State with `useReducer` Hook

{% tabs %}
{% tab title="App.js" %}

```jsx
// Import the React library and the useReducer hook from the react package
import { React, useReducer } from "react";
// Import the App.css stylesheet
import "./App.css";

// Define action types as constants
const INCREMENT = "increment";
const DECREMENT = "decrement";
const INPUTVAL = "change-value";
const ADD_VAL_TO_COUNT = "add-value";

// Define a reducer function that accepts a state and an action, and returns a new state
const reducer = (state, action) => {
  switch (action.type) {
    case INCREMENT:
      // Return a new state object with the count incremented by 1
      return {
        ...state,
        count: state.count + 1,
      };
    case DECREMENT:
      // Return a new state object with the count decremented by 1
      return {
        ...state,
        count: state.count - 1,
      };
    case INPUTVAL:
      // Return a new state object with the inputValue property set to the payload of the action
      return {
        ...state,
        inputValue: action.payload,
      };
    case ADD_VAL_TO_COUNT:
      // Return a new state object with the count incremented by the value of the inputValue property, and the inputValue property reset to 0
      return {
        ...state,
        count: state.count + state.inputValue,
        inputValue: 0,
      };
    default:
      // If the action type doesn't match any of the cases above, return the current state
      return state;
  }
};

// Define a functional component called App
function App() {
  // Declare a variable called initialCount and set it to 50
  let initialCount = 50;

  // Use the useReducer hook to create a state object and a dispatch function based on the reducer function and an initial state object
  const [state, dispatch] = useReducer(reducer, {
    count: initialCount,
    inputValue: 0,
  });

  console.log(state);

  // Define a function called increment that dispatches an action with the type INCREMENT
  function increment() {
    dispatch({
      type: INCREMENT,
    });
  }

  // Define a function called decrement that dispatches an action with the type DECREMENT
  function decrement() {
    dispatch({
      type: DECREMENT,
    });
  }

  // Define a function called handleChange that updates the inputValue property of the state object based on the value of an input field
  function handleChange(e) {
    const inputValue = parseInt(e.target.value) || 0;

    dispatch({
      type: INPUTVAL,
      payload: inputValue,
    });
  }

  // Define a function called handleSubmit that dispatches an action with the type ADD_VAL_TO_COUNT
  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: ADD_VAL_TO_COUNT,
    });
  }

  // Render the following JSX elements
  return (
    <>
      <h1>{state.count}</h1>

      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>

      <form onSubmit={handleSubmit}>
        <input
          type="number"
          value={state.inputValue || ""}
          onChange={handleChange}
        />
        <button>Add it!</button>
      </form>
    </>
  );
}

// Export the App component as the default export of the module
export default App;

```

{% endtab %}

{% tab title="index.js" %}

```jsx
import React from "react"
import ReactDOM from "react-dom"

import App from "./App"

ReactDOM.render(<App />, document.getElementById("root"))

```

{% endtab %}
{% endtabs %}

## Managing more Complex State with `useReducer` Hook

{% tabs %}
{% tab title="Todo.js" %}

```jsx
import React from "react"
import {ACTIONS} from "./App"

function Todo({todo, dispatch}){
    return(
        <div style={{ color: todo.complete ? "#AAA" : "#000" }}>
            <span>{todo.name}</span>
            <button onClick={()=> dispatch({ type: ACTIONS.TOGGLE_TODO, payload: {id:todo.id} })}>Toggle</button>
            <button onClick={()=> dispatch({ type: ACTIONS.DELETE_TODO, payload: {id:todo.id} })}>Delete</button>
        </div>
    )
}

export default Todo
```

{% endtab %}

{% tab title="App.js" %}

```jsx
import React, {useReducer, useState} from "react"
import Todo from "./Todo"

export const ACTIONS = {
    ADD_TODO: 'add-todo',
    TOGGLE_TODO: 'toggle-todo',
    DELETE_TODO: 'delete-todo',
}

// `reducer` function takes 2 parameters - `Current State` and `action`. It returns a `New State`.
function reducer(todos, action){
    switch(action.type){
        case ACTIONS.ADD_TODO:
            return [...todos, newTodo(action.payload.name)]
        
        case ACTIONS.TOGGLE_TODO:
            return todos.map(todo =>{
                if (todo.id === action.payload.id){
                    return {...todo, complete: !todo.complete} // if id does match return a new list of items with altered 'complete' state
                }
                return todo // do nothing if id doesn't match
            })
        
        case ACTIONS.DELETE_TODO:
            return todos.filter(todo => todo.id !== action.payload.id) // Only keep the items with unmatched id's
            
        default:
            return todos
    }
}

function newTodo(name){
    return { id: Date.now(), name: name, complete:false }
}

function App() {
    
    // useReducer hook accepts 2 parameters - a `reducer function`  and a `value`(as object)
    const [todos, dispatch] = useReducer(reducer, [])
    const [name, setName] = useState("")
    console.log(todos)
    
    function handleSubmit(e){
        e.preventDefault()
        dispatch({ type: ACTIONS.ADD_TODO, payload: {name:name} }) // passing `name` parameter to be used within `reduce` function
        setName("")
    }
    
    return (
        <div>
           <form onSubmit={handleSubmit}>
                <input type="text" value={name} onChange={ (e) => setName(e.target.value)}></input>
           </form>
           {
               todos.map(todo => {
                   // Provide `dispatch` function prop to be used in `Todo` Component
                   return <Todo key={todo.id} todo={todo} dispatch={dispatch} />
               })
           }
        </div>
    )
}

export default App

```

{% endtab %}

{% tab title="index.js" %}

```jsx
import React from "react"
import ReactDOM from "react-dom"

import App from "./App"

ReactDOM.render(<App />, document.getElementById("root"))

```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
`payload` is an object that defines all the variables the actions should be applied on.
{% endhint %}

## Using `immer` with `reducer()`

We can directly mutate state with immer package.

```jsx
// Import the produce function from the immer package
import { produce } from "immer";

// Import the React library and the useReducer hook from the react package
import { React, useReducer } from "react";

// Import the App.css stylesheet
import "./App.css";

// Define action types as constants
const INCREMENT = "increment";
const DECREMENT = "decrement";
const INPUTVAL = "change-value";
const ADD_VAL_TO_COUNT = "add-value";

// Define a reducer function that accepts a state and an action, and returns a new state
const reducer = (state, action) => {
  switch (action.type) {
    // If the action type is INCREMENT, update the count property of the state by adding 1
    case INCREMENT:
      state.count = state.count + 1;
      return;

    // If the action type is DECREMENT, update the count property of the state by subtracting 1
    case DECREMENT:
      state.count = state.count - 1;
      return;

    // If the action type is INPUTVAL, update the inputValue property of the state with the action payload
    case INPUTVAL:
      state.inputValue = action.payload;
      return;

    // If the action type is ADD_VAL_TO_COUNT, add the value of the inputValue property to the count property, and set the inputValue property to 0
    case ADD_VAL_TO_COUNT:
      state.count = state.count + state.inputValue;
      state.inputValue = 0;
      return;

    // If the action type is not recognized, return the state as is
    default:
      return;
  }
};

// Define a functional component called App
function App() {
  // Declare a variable called initialCount and set it to 50
  let initialCount = 50;

  // Use the useReducer hook to create a state object and a dispatch function based on the reducer function and an initial state object
  const [state, dispatch] = useReducer(produce(reducer), {
    count: initialCount,
    inputValue: 0,
  });

  // Log the state object to the console
  console.log(state);

  // Define a function called increment that dispatches an action with the type INCREMENT
  function increment() {
    dispatch({
      type: INCREMENT,
    });
  }

  // Define a function called decrement that dispatches an action with the type DECREMENT
  function decrement() {
    dispatch({
      type: DECREMENT,
    });
  }

  // Define a function called handleChange that updates the inputValue property of the state object based on the value of an input field
  function handleChange(e) {
    const inputValue = parseInt(e.target.value) || 0;

    dispatch({
      type: INPUTVAL,
      payload: inputValue,
    });
  }

  // Define a function called handleSubmit that dispatches an action with the type ADD_VAL_TO_COUNT
  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: ADD_VAL_TO_COUNT,
    });
  }

  // Render the following JSX elements
  return (
    <>
      <h1>{state.count}</h1>

      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>

      <form onSubmit={handleSubmit}>
        <input
          type="number"
          value={state.inputValue || ""}
          onChange={handleChange}
        />
        <button>Add it!</button>
      </form>
    </>
  );
}

// Export the App component as the default export of the module
export default App;

```
