Disclaimer. I understand that Redux offers more than global state. What I’m offering here is an elegant global state, with no dependencies but React 16.8+

React Hooks was announced at the end of 2018. Then it was released in March 2019. Recently I saw the React Conf video where Dan Abramov and Ryan Florence announces and demonstrates the power of Hooks. Ryan implements a Reducer with Hooks, while slowly picking apart the good old React class component. It is brilliant and you should see the 2 speaks here: (you can skip to Dan’s part)

The presentation ended with some loose ends. How can the reducer become globally available while listening to state changes? He actually *whispers* during the end of the video:

And then you add Context…

Suddenly, a new world opened.

Create React App.

Import useReducer and useContext from React.

Create a reducer switch case.

Create an initial state.

Pass state and dispatch to whichever component with 1 line (!!) of code.

And you got global state, native with React! A lot of articles has appeared on Medium regarding the useReducer+useContext setup, giving you a global state with a reducer, without npm install redux redux-react.

I started to slowly refactor my Tech Radar Collaboration app, just like Dan and the docs suggest. Then I went overboard and tore my app apart. I had 4 different StoreContext.js files trying to achieve the same thing, testing out different implementations while following guides. What I ended up with was a mix of many solutions.

This is the solution I’ve ended up with

Reduce React State with Hooks and Context

In your project, create a /context directory. Add 3 files: actions.js, reducers.js, StoreContext.js. Together with these 3 files and a React Component, you’ll make great things happen!

I’ve created a demo application in code sandbox. This shows how few lines of code you can get a global state store with Hooks. If you prefer interactivity, check out this demo. Further reading includes copies of the files from this project.

What is going on here?

actions.js

Defines useActions(state, dispatch) using the Hooks convention with prefixed ‘use’. All of the functions defined in useAction() { … } will be available in the actions in useContext(). Actions are for defining more advanced logic than a pure dispatch(type, payload) function would do.

import { types } from "./reducers"; export const useActions = (state, dispatch) => {

function addTechIfNotInList(newTech) {

const techIndex = state.techList.indexOf(newTech);

if (techIndex !== -1) {

alert("Tech is defined in list");

} else {

dispatch({ type: types.ADD_TO_TECH_LIST, payload: newTech });

}

} return {

addTechIfNotInList

};

};

With Redux, there’s a fine balance to where logic should go; reducers or action creators. Find a pattern which works for you!

reducers.js

In this file, I’ve defined the initialState, types and the reducer itself. When we dispatch a type from anywhere in the app, it will match the switch conditional and perform the correct operation on state. If you’re familiar with Redux, this should seem very familiar to you!

const initialState = {

techList: ["TypeScript", "React Hooks"]

}; const types = {

SET_TECH_LIST: "SET_TECH_LIST",

ADD_TO_TECH_LIST: "ADD_TO_TECH_LIST",

REMOVE_FROM_TECH_LIST: "REMOVE_FROM_TECH_LIST"

}; const reducer = (state = initialState, action) => {

switch (action.type) {

case types.SET_TECH_LIST:

return {

...state,

techList: action.payload

};

case types.ADD_TO_TECH_LIST:

return {

...state,

techList: [...state.techList, action.payload]

};

case types.REMOVE_FROM_TECH_LIST:

return {

...state,

techList: state.techList.filter(

tech => tech !== action.payload)

};

default:

throw new Error("Unexpected action");

}

};

export { initialState, types, reducer };

You can see that I’ve chosen to include some logic in this Reducer. When removing tech, it’s enough to dispatch(REMOVE_FROM_TECH, techObj). However, when adding technology it will always add it to the state. That’s the logic I created in the Action Creator.

More advanced logic should go in action creators, such as API calls and other side-effects making your functions “not pure”. Basic array, object and string operations can also be added into the reducer.

StoreContext.js

This is where we use Context to make our store globally retrievable. This would be equivalent to Redux’ createStore(), only… different.

The code in this file is

const StoreContext = createContext(initialState); const StoreProvider = ({ children }) => {

// Get state and dispatch from Reacts new API useReducer.

const [state, dispatch] = useReducer(reducer, initialState);

// Get actions from useActions and pass it to Context

const actions = useActions(state, dispatch); // Log new state

useEffect(() => console.log({ newState: state })},[state]); // Render state, dispatch and special case actions

return (

<StoreContext.Provider value={{ state, dispatch, actions }}>

{children}

</StoreContext.Provider>

);

}; export { StoreContext, StoreProvider };

Now, you can use the Context in all your components which are children of the exported StoreProvider. Just initiate it with useContext(StoreContext) and get the state, dispatch, actions objects.

App.js

With all this setup, you can clean up your components a lot! Only 1 line is required to get state and all of your actions.

const { state, dispatch, actions } = useContext(StoreContext);

Just make sure that App.js is a child of StoreProvider exported from StoreContext.js