React Context - State Management

React Context - State Management

State Management

State management means accessing, initializing, modifying, and deleting the state in your react application.

Not always but often you will find yourself in the middle ground of what to use to manage the state of your application, The use of a third-party library like redux might seems unnecessary as the state is not that big but on the other hand, managing it in the local state and sharing via prop drilling or lifting doesn't seem like the right solution.

This is the correct and most suitable scenario to use React's Context API for state management.

So let's get started, our project state will hold the user's auth state and share the state to the navbar or as per the requirement of the components. The Navbar will show a login or logout button depending upon the user state

Our initial state will look like the below JSON object.

// src/Context.js

import React, { useState } from "react";

  let [auth, setAuth] = useState(false);

  const login = () => {
    setAuth(true);
  };

  const logout = () => {
    setAuth(false);
  }

The first step to using the context is to create the context

// src/Context.js

import React, {useState, createContext} from 'react';

const StateContext =  createContext();
  let [auth, setAuth] = useState(false);

  const login = () => {
    setAuth(true);
  };

  const logout = () => {
    setAuth(false);
  }


export default StateContext;

To Share our state inside our application we need to wrap the root level component via the Provider component of our StateContext and pass our state inside the value prop of the provider component

Let's start by creating a new Component StateContextWrapper which will return our StateContext Provider Component.

// src/Context.js

import React, { useState, createContext } from "react";

const StateContext = createContext();

const StateContextWrapper = () => {

  let [auth, setAuth] = useState(false);

  const login = () => {
    setAuth(true);
  };

  const logout = () => {
    setAuth(false);
  };


  return (
    <StateContext.Provider value={{
      auth: auth,
      onLogin: login,
      onLogout: logout
    }}>
    </StateContext.Provider>
  );
};

export { StateContextWrapper };

export default StateContext;

But will need the StateContextWrapper to be a Higher-Order Component(HOC), You may ask what does it do? A HOC takes one component as input and returns a new component as Output. In Our case, it will take our root level component as input and return it Wrapped inside the StateContext.Provider with the value prop so we can access whatever is provided inside the value prop it all over the app or children components of the root component. We will use the children Prop for this as below

// src/Context.js

const StateContextWrapper = ({children}) => {
  return (
     <StateContext.Provider value={initialState}>  
            {children} 
     </StateContext.Provider>
    )
}

Our StateContextWrapper is ready now we just need to wrap it around the App Component the best place to do is Index.js

// src/Index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {StateContextWrapper} from './Context.js';

ReactDOM.render(
    <StateContextWrapper>
       <App />
   <StateContextWrapper/>
  document.getElementById('root')
);

The State is being shared but how do we use or consume inside our Navbar component We can do this via the StateContext.Consumer and the most easier way to do this is via the useContext Hook. let see how we can do that inside our navbar component

// src/Navbar.js

import React, { useContext } from 'react';
import StateContext from '/Context.js'

const Navbar = () => {
   const authState = useContext(StateContext);
    return ()
}

export default  Navbar

Now let's conditionally display the user auth state and buttons to toggle the state.

// src/Navbar.js

import React, { useContext } from "react";
import StateContext from "./Context";

function Navbar() {
  const authState = useContext(StateContext);

  return (
    <div>
      User is  {authState.auth ? 'Logged In' : 'Logged Out'} 
      <br/>
      <br/>
      {authState.auth ? (
        <button onClick={() => authState.onLogout()}> Logout </button>
      ) : (
        <button onClick={() => authState.onLogin()}> LogIn </button>
      )}
    </div>
  );
}

export default Navbar;

You can build upon this and pass on more data/state and methods inside the value prop of the provider component and it will be accessible all over the app or children component via the useContext hook or the consumer component.

The code for this application can be found at https://codesandbox.io/s/ancient-cloud-4790g