Implementing a global store using React Context and Hook patterns: Part 1

‘Prop Drilling’ through the React component tree is one of the worst nightmares for developers. For those who don’t know what prop drilling is, it’s the process of passing down data/methods from parent to children within a long hierarchy. It’s not a problem when the tree is small, with one or two-level. But when the React component gets larger, it’s troublesome to keep track of all that ‘life-altering’ props.

To solve prop drilling and some related problems, global state management like ReduxMobxUnstatedApollo Link State, etc. has become unrequitedly popular. Also for React, react-redux has become the oneway destination to React developers.

But, sometimes for a simple app using those state management solutions might seem overkill. Here comes React to the rescue. Using React Context API and Hook patterns we can create a Redux-like store, that we can use to store data, call on dispatcher for performing actions, and thus solving the problem of prop drilling without installing any third party state management systems. Voila!

This will be a two-part article, in the first part, we are going to create a redux like store, let’s call it ‘react-store’ from now on. Then in the next part, we are going to create a simple react app, to apply that react-store practically.

Let’s Get Started

To get started we need to understand how React Context works. The best way to learn about the Context is to go through the Official Documentation. In short, Context is a way to share data across the react-app without having to pass them explicitly (remember, prop drilling). So, we will create a context, then wrap our app with it and any state we keep on the context will be available throughout the app. Seems simple enough. Actually, we will see that it is.

Then comes, React Hooks. After being included in React’s latest releases, the hooks have become such a buzzword, to be honest, it’s actually deserving. We are going to use useReducer hook (Wait, where have I heard the name reducer before? Yeah, right in redux), which to be honest is the more advanced version of useState hook. To know more about them go through their Documentation as usual.

Okay, enough chit-chat. Let’s create our desired context:

const StoreContext = createContext();
const GlobalStore = props => {
  return <StoreContext.Provider value={{}} />;
};

We have created a context called StoreContext. And in the GlobalStore function, we are using StoreContext.Provider to expose the values throughout the app. Now, let’s add state to our context.:

const StoreContext = createContext();
const GlobalStore = props => {
  if (props === undefined)
    throw new Error(
      "Props Undefined. You probably mixed up betweenn default/named import"
    );
  const { load, ...rest } = props;
  const [state, dispatch] = useReducer(load.reducer, load.initialState);
  return <StoreContext.Provider value={{ state, dispatch }} {...rest} />;
};

So, we have introduced a local state for the whole context using useReducer. It returns state which have the initial state of the app that comes from prop load.initialState and dispatch that can takes actions on the state through the load.reducer. We will create a custom hook using useContext to expose the values. So, the final file GlobalStore.js will look like this:

import React, { createContext, useContext, useReducer } from "react";
const StoreContext = createContext();
const GlobalStore = props => {
  if (props === undefined)
    throw new Error(
      "Props Undefined. You probably mixed up betweenn default/named import"
    );
  const { load, ...rest } = props;
  const [state, dispatch] = useReducer(load.reducer, load.initialState);
  return <StoreContext.Provider value={{ state, dispatch }} {...rest} />;
};
export const useStore = () => useContext(StoreContext);
export default GlobalStore;

We will now create a combineReducer function, just like Redux to combine multiple reducers if needed. The combineReducer.js file will look like this:

const combineReducer = reducers => {
  const finalReducers = {};
  Object.keys(reducers).forEach(key => {
    if (typeof reducers[key] === "function") finalReducers[key] = reducers[key];
    else throw new Error(`${key} reducer must be a function`);
  });
  const finalReducerKeys = Object.keys(finalReducers);
  return (state, action) => {
    let hasChanged = false;
    const nextState = {};
    finalReducerKeys.forEach(key => {
      const reducer = finalReducers[key];
      const previousStateForKey = state[key];
      const nextStateForKey = reducer(previousStateForKey, action);
      nextState[key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    });
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length;
    return hasChanged ? nextState : state;
  };
};
export default combineReducer;

We will also create a createStore.js file just to create an object out of reducer and initialState. It’s so trivial.

const createStore = (reducer, initialState) => ({ reducer, initialState });
export default createStore;

TL;DR: This whole code can be found here.

Well, that was easy. We have our lightweight react-store ready to be used. In the next article, we will create a simple app and try to use this global store firsthand. This will be exciting. So for now, let me quote Charles Bukowski:

If you’re going to try, go all the way. There is no other feeling like that. You will be alone with the gods, and the nights will flame with fire. You will ride life straight to perfect laughter. It’s the only good fight there is.
Picture of Tusher Ahamed

Tusher Ahamed

Software Development Engineer II

Hire Exceptional Developers Quickly

Share this blog on

Hire Your Software Development Team

Let us help you pull out your hassle recruiting potential software engineers and get the ultimate result exceeding your needs.

Contact Us Directly

Address:

Plot # 272, Lane # 3 (Eastern Road) DOHS Baridhara, Dhaka 1206

Talk to Us
Scroll to Top