DEV Community

loading...
Cover image for React Context Custom Hook | The only Global State you'll ever need

React Context Custom Hook | The only Global State you'll ever need

imervinc profile image 👺Mervyn ・3 min read

With a bunch of state management library out there. All you need is something you already have. React Context with the help of hooks can be your go-to for smaller project.

Making custom hooks is one of React concept you will need to know.
Lately I have been using a custom hook to handle my global state for ease of use.

Custom Hook

Here is an example.

Always name your custom hook starting with use, so react will treat it as a hook.

useCtxDark.jsx

import { useState, createContext, useContext, useMemo } from 'react'

const darkContext = createContext(null)

export const DarkProvider = ({ children }) => {
  const [dark, setDark] = useState(false)

  const darkValue = useMemo(() => [dark, setDark], [dark])

return 
  <darkContext.Provider value={darkValue}>
    {children} 
  </darkContext.Provider>
}

export default function useCtxDark() {
  return useContext(darkContext)
}

Enter fullscreen mode Exit fullscreen mode

In our custom hook we define our createContext(), and a state either useState or useReducer where we store data. Then we store that data to memoize it in useMemo to reduce re-renders.

Notice that we have 2 exports. A named export which is gonna be our Provider that wraps our app and a default export which is our hook that is used to get and set our data.

Now we setup it up by wrapping our app with our Context Provider

App.jsx

import { DarkProvider } from '@/hooks/useCtxDark'

export default function App() {
  return (
    // Wrapper
    <DarkProvider>
      <Nav />
      <Pages />
    </DarkProvider>
  )
}
Enter fullscreen mode Exit fullscreen mode

Then we us it like a hook and have access to the global state where ever we call this hook.

DarkToggle.jsx

import useCtxDark from '@/hooks/useCtxNav'

const Nav = () => {
  const [dark, setDark] = useCtxDark()

  return <input type='checkbox' onChange={() => setDark(!dark)} />
}
Enter fullscreen mode Exit fullscreen mode

Here is an example using useReducer

import { useReducer, useContext, createContext, useMemo } from 'react'

const globalContext = createContext(null)

const initialState = {
   todo: []
}

const reducer = (state, action) => {
   switch (action.type) {
    case "ADD":
      return { todo: [...state.todo, action.payload] };
    case "DELETE":
      const filltered = state.todos.filter((x) => x.id !== action.payload)
      return {
        ...state,
        todos: [...filltered],
      }
    default:
      return state;
  }
}

export const GlobalProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState)

  const stateValue = useMemo(() => [state, dispatch], [state])

return 
  <globalContext.Provider value={stateValue}>
    {children} 
  </globalContext.Provider>
}

export default function useCtxDark() {
  return useContext(globalContext)
}
Enter fullscreen mode Exit fullscreen mode

And you can make multiples of these together when you need more!

Providers.jsx

import { DarkProvider } from '@/hooks/useCtxDark'
import { NavProvider } from '@/hooks/useCtxNav'

const Providers = ({children}) => {
  return(
    <DarkProvider>
      <NavProvider>
        {children}
      </NavProvider>
    </DarkProvider>
  )
}
Enter fullscreen mode Exit fullscreen mode

Multi-Context

Another version, where you make a separate context for your state and dispatch.

useStore.jsx

import { useState, createContext, useContext, useMemo } from 'react'

const storeContext = createContext(null)
const dispatchContext = createContext(null)

export const StoreProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState)

return 
  <dispatchContext.Provider value={dispatch}>
   <storeContext.Provider value={state}>
    {children}
   </storeContext.Provider> 
  </darkContext.Provider>
}

export function useCtxStore() {
  return useContext(storeContext)
}

export function useCtxDispatch() {
  return useContext(dispatchContext)
}

Enter fullscreen mode Exit fullscreen mode

Then import just the hook for store

import {useCtxStore, useCtxDispatch} from './useStore'

const Component = () => {
  const {todos} = useCtxStore()
  const dispatch = useCtxDispatch()

  const clickHandler = (id) => {
    dispatch({type: '', payload: id})
  }

  return(
    <ul>
     {todos.map((item) => 
       <li key={item.id} onClick={() => clickHandler(item.id)}> 
         {item.name}
       </li> 
     )}
    </ul>
  )
}
Enter fullscreen mode Exit fullscreen mode

Here is a live example with all the above hooks

If your planning to do get some asynchronous data. I do recommend that you use a library for data fetching like React Query or SWR for better UX and Dev Exp.

A perfect pair to React Context for a light weight State Management for both Global and Synchronous State!

Discussion (0)

Forem Open with the Forem app