DEV Community

Rahman Fadhil
Rahman Fadhil

Posted on • Originally published at rahmanfadhil.com

Global State with React Hooks

Read the original article here

React Hooks let you use state and other React features in functional components. On the other hand, React Context provides a way to pass data around components tree having without pass props down manually.

With both features combined, we can now build our very own state management without using third-party libraries. Besides making our web applications lighter, our code is much more efficient and readable.

Disclaimer

First disclaimer, these methods are not the best practice to manage global state (use Redux instead). Second, there some third party libraries out there that are similar to what we are doing in this tutorial. If those are what you're looking for, definitely check out something like Unstated Next or Easy Peasy.

What we're going to build?

We are going to build a simple counter app which just increases a number by clicking a button. Our state management system will look like Redux (using reducer function and dispatcher). Finally, we're going to be able to scale up our app by merging two (or more) reducers functions (part 2).

I have published a completed version of this project right here.

Global state hook

// ./useGlobalState.js

import React, { createContext, useReducer, useContext } from "react"

const StateContext = createContext(null)

export function Provider({ children, reducer, initialState }) {
  return (
    <StateContext.Provider value={useReducer(reducer, initialState)}>
      {children}
    </StateContext.Provider>
  )
}

export function useGlobalState() {
  return useContext(StateContext)
}

Our state management is very straight forward. We store our entire state inside our StateContext context, then we have a Provider component which we will use later to provide our global state to make all components inside our component tree can access it.

Finally, we define our useGlobalState custom hook which we will use inside our components where we want to access our global state.

Providing state

We have our state management ready, now we need to create our state and provide it inside App component.

First thing first, we declare our reducer function to mutate our state. At the moment, we only able to handle INCREMENT action to increase our counter state.

function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1
    default:
      return state
  }
}

You notice that we change our state by returning a new value. That's because our state is immutable, we can't change our state by reassigning new value. That is one important thing to keep in mind when using useReducer.

Then, we need to define our initial state. Our counter app should start by 0.

const initialState = 0

Finally, our App must provide our state with Provider component to make other components inside our component tree can access it. Provider component requires reducer function and an initial state as props. Let's both of them as we defined earlier.

export default function App() {
  return (
    <Provider reducer={reducer} initialState={initialState}>
      <Counter />
    </Provider>
  )
}

If you've done it right your code should be look like this.

// ./App.js

import React from "react"

import { Provider } from "./useGlobalState"
import Counter from "./Counter"

function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1
    default:
      return state
  }
}

const initialState = 0

export default function App() {
  return (
    <Provider reducer={reducer} initialState={initialState}>
      {/* Place your components here */}
    </Provider>
  )
}

Accessing global state

To test our state works, let's try to access our global state from Counter component.

// ./Counter.js

import React from "react"
import { useGlobalState } from "./useGlobalState"

export default function Counter() {
  const [state, dispatch] = useGlobalState()

  return (
    <div>
      <h1>Counter: {state}</h1>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>Increase</button>
    </div>
  )
}

In this component, we display our state inside an h1. We're trying to call INCREMENT action whenever our "Increase" button has been clicked.

Try to run the app. If you don't see any error, you followed this tutorial correctly.

Here is the code of this project:

Top comments (0)