Building Redux with React Hooks. React Context + useReducer = magic!

Subscribe to my newsletter and never miss my upcoming articles

Redux is a state management solution for web applications. Although it is widely used with React, it can be used with any Javascript app. Although Redux is a great state management solution, it is boilerplate-y in nature and adds to the overall size of your app.

React is a UI library that does not ship with its own state management solution - or does it?

React Context API

In a typical React application, data is passed top-down (parent to child) via props, but this can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the tree.

On its own, the context api is not a substitute for Redux since you cannot replicate the complex action-reducer paradigm without other hooks

Disclaimer

A word of disclaimer before we start. I would suggest that the method i am documenting here only be used in small projects. If you are building something huge, you should still use Redux. It provides a lot more functionality through Thunks, Saga and Reselect.

The solution

Sharing the auth state with all the components of your component tree is a common usecase. Let's implement that using the context api and the useReducer hook.

Use the useReducer hook to create a reducer function

import React, { useReducer } from "react";

const initialState = {
  user: null,
};

export const AUTH_STATE_CHANGED = "AUTH_STATE_CHANGED";

const reducer = (state, action) => {
  switch (action.type) {
    case AUTH_STATE_CHANGED:
      return {
        user: action.payload,
      };
  }
  return state;
};

I have created a simple reducer function similar to what you would see in a Redux project by passing the hook a reducer function and an initial state.

Using the React Context API, we can now create a context that we want to drill down the app. The authState object is the state that you want to be passed down to your components and actions object contains all the actions that you would typically use with Redux. The useReducer hook returns a dispatch function just like Redux

const AuthContext = React.createContext();

const AuthProvider = (props) => {
  const [authState, dispatch] = useReducer(reducer, initialState);

  const actions = {
    authStateChanged: (user) => {
      if (user) {
        dispatch({ type: AUTH_STATE_CHANGED, payload: user });
      } 
    },
  };

  return (
    <AuthContext.Provider
      value={{
        authState: authState,
        authActions: actions,
      }}
    >
      {props.children}
    </AuthContext.Provider>
  );
};

We can now export this

export { AuthProvider, AuthContext };

Wrap the component you want to access the state from. Since i want to be able to access the authState from anywhere in my app, I will wrap my App component. If you do not want the whole app to be able to access the state, you can scope the state by selectively wrapping the components that need to be able to access the state

import { AuthProvider } from "./authContext";

export default function App() {
    return (
      <AuthProvider>
        <Login />
      </AuthProvider>
    );
  }
}

Now to access the state from any component inside my app eg. Login screen

import { AuthContext } from "./authContext";
const Login = (props) => {
  const { authState, authActions } = React.useContext(AuthContext);

  const login = () => {
    authActions.authStateChanged({ name: "Burhanuddin" });
  }

  return (
    <div>
      {authState.user.name}
      <button onClick={() => login()}>
        Login
      </button>
    </div>
  );
};

With this you can replicate Redux inside React without any external dependencies

No Comments Yet