React’s useContext API – Many Examples


Uncategorized

Updated Apr 12th, 2023

There are two good examples of using React’s useContext API to manage app-wide state:

RFTROU Course: Using CRA and UseReducer (Really useImmerReducer) – link to notes here.

MAX NEXT JS Course: Using Next JS and useState (no useReducer) – link to notes here.

Link to Web Dev Simplified article and video here.

Differences

I need to do some messing around to combine the two approaches, useContext API, using useImmerReducer, in a Next JS app to manage appState and appDispatch.

I like the RFTROU approach because it manages state with useImmerReducer and it separates out app-wide dispatch and app-wide state. I don’t like that the initial state, useReducer function are in the “Main.js” file. This would be the “_app.js” file in Next JS app and it seems like it would be crowding this file.

Max’s example is good in that he seems to move the initial state and state management to the file that creates the context but he doesn’t use “useReducer” and so it is not a perfect match.

Max created a store folder to keep his context files, with more complicated apps you could have many and this would come in handy.

Instead of broad StateContext.js and DispatchContext.js files Max had a more specific “notification-context.js” file. He passed createContext() an object with key/values. He said this is an initialization object that describes the structure, setting up a base context and getting better auto-completion.

const NotificationContext = createContext({
  notification: null, // { title, message, status }
  showNotification: function (notificationData) {},
  hideNotification: function () {},
});

Max also differed by saying the context created in this file allows you to create a component, a Provider Component, which we can wrap around components (and their child components) that can now tap into this context. So he changes the named constant variable to an uppercase letter to signal, make it clear, we can get a component out. The goal is to wrap components in “_app.js” file in context. However we don’t just want to provide context like this but want to create a separate component that also manages all of he context-related state using “useState()”. So back in the context file in the store folder there is not just createContext() exported as a default but also a component created in that file.

function NotificationContextProvider(props) {
  return (
    <NotificationContext.Provider value={context}>
      {props.children}
    </NotificationContext.Provider>
  )
}

The reason he does it this way is so we can use the “NotificationContextProvider” component to wrap around other components which will then automatically have access to all our context. So now it’s the “NotificationContextProvider” function/(component?) that you want to wrap around all necessary components in the “_app.js” file.

See the link above for the full code and write-up on this approach.

Uses this separate context file to not only “createContext” which is exported as default as usual but another component that manages the state in this file (could use useReducer or useState for this) and creates a context object that is passed to inside the value={context}.

To make it more complicated, you can’t show notification in “_app.js” file because this is where we do the wrapping so he leverages the Layout.js component since this is also wrapping all components. In the “Layout.js” file, import Notification and NotificationContext (but not NotificationContextProvider)

In the separate context file, there are two things exported, 1.) the default export (named constant variable set to useContext()) which is what is imported into the components that consume and 2.) a named function/component export that manages the state and returns JSX that set’s up the wrapping, This component is what is used to wrap all components.

Head is spinning…

Similarities

Both imported {createContext} from ‘react’ and set a named constant variable createContext().

Both set named constant as default export.

Early Thoughts

Have separate file, create context and manage state with useReducer. Once useReducer is working swap out for useImmerReducer.

//in appWideContext.js file

const appWideContext = createContext()

export function appWideContextProvider(props) {

const initialState = {whatevs}
ourReducer function

const [] = useReducer(ourReducer, initialState)

return (
  <appWideContext.Provider value={?}>
    {props.children}
  </appWideContext.Provider>
)

}

export default appWide Context

// in "_app.js" wrap all components in <appWideContextProvider> tags...

Failing to wrap my head around if you move the state to a new file how you can still separate state from dispatch using the useReducer hook.

Could we do something like:

return (
  <appWideStateContext.Provider value={state}>
    <appWideDispatchContext.Provider value={dispatch}>
      {props.children}
    </appWideDispatchContext.Provider>
  </appWideStateContext.Provider>
)

My head feels like it is going to explode…

Good State for Context

Mobile menu open or not

UserLoggedIn or Not

FlashMessages

Additional Resources

Note on useReducer

The useReducer hook, and useImmerReducer is a great way to manage complex state logic including app-wide state but it is not the only way. You can have useState manage app-wide state and useReducer to manage a specific component’s state. I’m sure it’s a time saver to set it up correctly first but it can be converted after the fact.

Another Example

Here we see state and dispatch, useReducer managing state in useContext

// initialized global store

export const quizContext = createContext();

// reducer

const reducer = (state, action) => {
  switch (action.type) {
    case types.USER_LOGIN_SUCCESS:
      return // user

    case types.USER_LOGOUT_SUCCESS:
      return // clear user

    default:
      return state;
  }
};

const QuizContextProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, {
    user: null,
  });

  return (
    <quizContext.Provider value={{ dispatch, state }}>
      {children}
    </quizContext.Provider>
  );
};

export const useQuizContext = () => useContext(QuizContext);

export default QuizContextProvider;

UseReducer in Separate File

This article shows not only having useContext in a separate file but the reducer function and initial state as well.

export const initialState = {
  number: 0import { createContext, useContext, useMemo, useReducer } from "react";
import { AppReducer, initialState } from "./AppReducer";

const AppContext = createContext();
export function AppWrapper({ children }) {
   const { state, dispatch } = useReducer(AppReducer, initialState);
   const contextValue = useMemo(() => {
      return { state, dispatch };
   }, [state, dispatch]);
   
   return (
      <AppContext.Provider value={contextValue}>
         {children}
      </AppContext.Provider>
   );
}
export function useAppContext() {
   return useContext(AppContext);
}
};

export const AppReducer = (state, action) => {
  switch (action.type) {
    case "add_number": {
      return {
        ...state,
        number: action.value + state.number,
      };
    }
  }
};

Another Interesting One

Link here.