React Hooks: useReducer with useContext

The ContextAPI

Contex provides a way to pass data through the component tree without having to pass props down manually at every level.

Use of the ContextAPI prior the introduction of hooks

Step 1: Creation of the context

export const MyContext = React.createContext()

Step 2: Provide this context with a value

The provider must wrap the chlidren for the value to be available

<MyContext.Provider value={"myvalue"}>
<MyComponent/>
</MyContext.Provider>

Step 3: Consume the context value

First we need to import the context created and exported in App.js:

//in MyComponent.js
import {MyContext} from '../App'

Second we need to use the render props pattern to get hold of the value passed by the context:

return (
   <div>
      <MyContext.Consumer>
         {
            value => { 
               return <div>My value: {value}</div>
            }
         }
      </MyContext.Consumer>
   </div>
)

useContext hooks

With useContext the two first step remains the same. On the other hand, the conssumption of the context value is much simpler:

//in MyComponent.js
import React, {useContext} from 'react'
import {MyContext} from '../App'
function MyComponent() {
const value = useContext(MyContext)
   return (
      <div>
         {value}
      </div>
   )
}

useReducer

useReducer is a hook that is used for state management

It is an alternative to useState

What's the difference ? useState is built using useReducer

useReducer is related to reducer functions

useReducer(reducer, initialState)

reducer(currentState, action)

Reminder about javascript reduce array helper

Reducers signature: functions that takes 2 parameters, accumulator and current and that return a single value.

the reduce() method takes also two parameters: a reducer function and an initial value that the reducer function can make use of.

reduce and useReducer are very similar.

//reduce in javascript
array.reduce(reducer, initialValue
singleValue = reducer(accumulator, itemValue)
//reduce method returns a single value

//useReducer in React
useReducer(reducer, initialState)
newState = reducer(currentState,action)
//useReducer returns a pair of values. [newState, dispatch]

useReducer in its simple form to emulate a counter

import React, {useReducer} from 'react'
const initialState = 0;
const reducer = (state, action) => {
   switch(action) {
      case 'increment':
         return state + 1;

      case 'decrement':
         return state - 1;

      default:
         return state;
   }
}
function myCounter() {
   const [count, dispatch] = useReducer(reducer, initialState)
   //to increment: dispatch('increment')
   //to decrement: dispatch('decrement')
}

Let's revisit the example with state and action as objects

const initialState = {counter:0};
const reducer = (state, action) => {
   switch(action.type) {
      case 'increment':
         return {counter:state.counter + 1};

      case 'decrement':
         return {counter:state.counter - 1};

      default:
         return state;
   }
}
function myCounter() {
   const [count, dispatch] = useReducer(reducer, initialState)
   //to increment: dispatch({ type: 'increment'})
   //to decrement: dispatch({ type: 'decrement'})
   //use count.counter to get the current value of the counter
}

useReducer with useContext

To enable global state management with useReducer you need to use useContext too.

To illustrate how to do it, the idea is now to share the counter value accross several components. Of course the idea is not to use the props!

//in app.js
export const CountContext = React.createContext();
const initialState = 0;
const reducer = (state, action) => {
   switch(action) {
      case 'increment':
         return state + 1;

      case 'decrement':
         return state - 1;

      default:
         return state;
   }
}

function App() {
   const [count, dispatch] = useReducer(reducer, initialState)
}

The goal is to provide to the context the count state and the dispath function.

<CountContext.Provider value={{countState:count, countDispatch:dispatch}}>
//the app sub components
</CountContext.Provider>

Now the children components who need to manipulate the count state can use the useContext hook to get count value and the dispatch function from the context value:

//in a child component
import CountContext from "../App"
function childComponent() {
   const countContext = useContext(CountContext);
   //now childComponent can acess countContext.countState and countContext.countDispatch
}