useReducer Hook+ Context API = A Powerful React Tool
Tags: react, webdev, javascript, frontend
Published: 2022-01-23 10:30:51

We are immensely familiar with hooks like `useState`, `useEffect` and `useRef` a lot which allows us to use class-based components features now in functional components. But React hooks have one more weapon in its arsenal which can be an effective tool to optimise a react application: the **`useReducer`** hook.

## useReducer - a redux wannabe
The best description and example of the `useReducer` hook can be found in the [Official React docs](https://reactjs.org/docs/hooks-reference.html#usereducer). But if I have to explain it in a concise manner: 
> useReducer allows your react component to have a redux-like state

You just need to provide a reducer function and an initial state value. Your component will get a `state` and a `dispatch` function which can be used to update that `state`. 

It seems similar to `useState`, and React specifies some deciding factor that can indicate when `useReducer` will be better alternative:
1. Your component state is complex that involves multiple sub-values, and/or
2. The next state value depends upon the current state value.

So a best example of `useReducer` can be like this:
```jsx
const initialTodos = [
    {
        id: 1,
        task: 'Sample Done task #1',
        done: true
    },
    {
        id: 2,
        task: 'Sample todo task #2',
        done: false
    }
]

function reducer (state, action) {
    switch(action.type) {
        case 'new_todo':
            return [
                ...state,
                {
                    id: state[state.length],
                    task: action.payload.task,
                    done: false
                }
            ]
        case 'edit_todo_task':
            const todoIdx = state.find( todo => todo.id===action.payload.id)
            return  [
                ...state.slice(0, todoIdx),
                {
                    ...state[todoIdx],
                    task: action.payload.task
                },
                ...state.slice(todoIdx+1)
            ]
        case 'toggle_todo_state': 
            const todoIdx = state.find( todo => todo.id===action.payload.id)
            return  [
                ...state.slice(0, todoIdx),
                {
                    ...state[todoIdx],
                    done: !state[todoIdx].state
                },
                ...state.slice(todoIdx+1)
            ]
    }
}

function TodoApp () {

    const [todos, dispatch] = useReducer(initialTodos, reducer)

    const handleStatusChange = (todoId) => {
        dispatch({
            type: 'toggle_todo_state',
            payload: { id: todoId}
        })
    }

    const handleTaskUpdate = (todoId, newTaskText) => {
        dispatch({
            type: 'edit_todo_task',
            payload: {
                id: todoId,
                task: newTaskText
            }
        })
    }
    const createNewTodo= (newTodoTask) => {
        dispatch({
            type: 'new_todo',
            payload: { task: newTodoTask }
        })
    }

    return (
        <TodoList
            todos={todos}
            onTodoCreate={createNewTodo}
            onStatusChange={handleStatusChange}
            onTaskEdit={handleTaskUpdate}
        />
    )
}
```

## A common and irritating use case in React Application
When using a complex component state like `useReducer`, we are likely to run into a scenario where we have to pass down the state updating function or a callback function (wrapping the state updating function) to the children components. If you have a large application, then it may happen that you have to pass those callback functions through intermediate children components until it reaches the actual descendant component which uses them. This can become unmanageable & suboptimal. 

## The Solution?
Combine the `useReducer` state & dispatch with the Context API.

> **The Context API**
>Context API have been a key feature of React. If you feel you need to be familiar with it, you can go through the [docs](https://reactjs.org/docs/context.html)


Both the state and the dispatch function produced by the `useReducer` can be fed to separate Context Providers in a parent component. Then any child component, no matter how deep, under the parent, can access them as needed with the use of `useContext` or Context Consumer.

Example:

```javascript
const TodosDispatch = React.createContext(null);
const Todos = React.createContext(null)

function TodoApp() {
  const [todos, dispatch] = useReducer(reducer, initialTodos);

  return (
    <TodosDispatch.Provider value={dispatch}>
        <Todos.Provider value={todos} >
            <TodoList />
        </Todos.Provider>
    </TodosDispatch.Provider>
  );
}



function TodoList() {
    const {todos} = useContext(Todos)

    return (
        <ul>
        {
            todos.map(todo => <TodoItem key={todo.id} task={task} isDone={todo.done} />)
        }
        </ul>
    )
}

function AddTodoButton() {
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({
        type: 'new_todo', payload: { task: 'hello' }});
  }

  return (
    <button onClick={handleClick}>Add todo</button>
  );
}
```

This combination helps to avoid passing down states or update functions through intermediate components.

Only the components who actually needs the state or the dispatch function can get what they need. 

The intermediate components get to handle lesser props as well and can better handle faster component re-rendering decision when memoized.


## Benefits
* This `useReducer` and `useContext` combination actually simulates Redux's state management, and is definitely a better light weight alternative to the PubSub library.
* If your application is already using an application state, and you require another application state(for whole or part of the application), the combination can be used as a 2nd application state 


## Caveat
This is not a perfect Redux alternative.
* Redux allows use of custom middlewares for better state management, but this feature is lacking in React's `useRecuder`.
* Async tasks can not be used with `useReducer`.
* Just like in Redux, there will be huge boilerplate code in the reducer function, and there are no APIs like Redux Tookkit to use for avoiding this.