React Hooks for State Management!(useContext, useEffect, useReducer)

Sean Urgel
Feb 10 · 5 min read

React 16.8

The React team officially release v16.8 a few hours ago and indeed it is a moment to celebrate 🎉. State Management has always been a problem from the day React was born, and we have constantly searching for solutions left and right. In this tutorial we’re going to see how to handle state management with useContext, useEffect and useReducer. What the heck are those?

Prerequisites ✅

  1. If you haven’t watched the React Conf video on Hooks, I suggest you do — Sophie Alpert, Dan Abramov and Ryan Florence give a very great introduction on hooks (every second is worth it!), I’ll just be covering the things that they didn’t talk about in this tutorial.

If you know those things already, let’s get started! 🔥

The app we’re going to create

Flow of the app

  1. Fetch count from server

1) Let’s start with the UI.

// App.jsimport React from 'react'function Counter() { return (
    <div>
      <h5>Count: 0</h5>
      {/* TODO: Increment */}
      <button onClick={() => {}}>+</button>      {/* TODO: Decrement */}
      <button onClick={() => {}}>-</button>
    </div>
  );
}function SeparateComponent() { return (
    <div>
      <h1>Shared Count: 0</h1>     {/* TODO: FETCH*/}
     <button onClick={() => {}}>
        Fetch Again
      </button>
    </div>
  );
}function App() {
  return (
    <div className="App">
        <Counter />
        <SeparateComponent />
    </div>
  );
}

Here we created two simple components that will later share count, let’s start by creating a component that can share state, by using React’s context API.

2) Creating a CounterContext

// Context.jsimport React from 'react'const initialState = {count: 0}const CounterContext = React.createContext(initialState);function CounterProvider(props) { return (
    <CounterContext.Provider value={initialState}>
      {props.children}
    </CounterContext.Provider>
  );
}export { CounterContext, CounterProvider };

We create a CounterContext that takes {count: 0} as the initialState and we export the Providers and Context.

3) Add the CounterProvider

// App.jsimport React from 'react'
import { CounterProvider } from './Context'// ...<div className="App">
  <CounterProvider>
    <Counter />
    <SeparateComponent />
  </CounterProvider>
</div>

We simply add the CounterProvider so that the consuming components can subscribe to when the context gets updated.

4) Add the Consumer

Now here’s where things finally get interesting. Traditionally we’d consume the CounterProvider like so.

// App.js...function SeparateComponent() {
  return (
    <CounterContext.Consumer>
      {({ count }) => (
        <div>
          <h1>Shared Count: {count}</h1>
          <button onClick={() => {}}>Fetch Again</button>
        </div>
      )}
    </CounterContext.Consumer>
  );
}

Instead we’re going to use the brand new useContext , here’s how we use it.

// App.jsimport React, { useContext } from 'react'...function SeparateComponent() {
  const { count } = useContext(CounterContext);return (
    <div>
      <h1>Shared Count: {count}</h1>
      {/* TODO: Fetch */}
      <button onClick={() => {}}>Fetch Again</button>
    </div>
  );
}

Wait… what just happened?

Stated in the documentation it says “Accepts a context object (the value returned from React.createContext) and returns the current context value, as given by the nearest context provider for the given context.”

Basically it just returns the value of the context provider! As you can see the code is looking more readable and if you’re consuming multiple contexts, we won’t get nested render functions!

Now you go ahead and try it on <Counter /> !

5) Adding State Management

Now that we shared a state between two separate components, let’s see how we can add state management implement increment & decrement .

If you’ve worked with Redux before, this is probably going to look familiar.

// Context.js// new
import React, { useReducer } from "react";// newlet reducer = (state, action) => {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + 1 };
    case "decrement":
      return { ...state, count: state.count - 1 };
    default:
      return;
  }
};const initialState = { count: 100 }
const CounterContext = React.createContext(initialState);function CounterProvider(props) {
  // new
  const [state, dispatch] = useReducer(reducer, initialState);return (
   // new
   <CounterContext.Provider value={{ state, dispatch }}>
      {props.children}
    </CounterContext.Provider>
  );
}export { CounterContext, CounterProvider };

So we did a couple of things here

  1. import useReducer

To those who have used state management and context before, I hope at this point you’re already getting the gist of what’s happening. Try stopping here and implementing it yourself if you can!

6) Get the shared state and dispatch objects in the consumer components

// app.jsfunction Counter() {
 
 // new
 const { state, dispatch } = useContext(CounterContext); return (
    <div>
      <h5>Count: {state.count}</h5>       {/* new */}
      <button onClick={() => dispatch({ type: "increment" })}>
        +
      </button>    {/* new */}
    <button onClick={() => dispatch({ type: "decrement" })}>
        -
     </button>
    </div>
  );
}function SeparateComponent() {  // new
  const { state } = useContext(CounterContext);return (
    <div>
      {/* new */}
     <h1>Shared Count: {state.count}</h1>
      <button onClick={() => {}}>Fetch Again</button>
    </div>
  );
}

Since we passed the state and dispatch objects as the value of the CounterContext.Provider we can subscribe to the shared state here, and dispatch the functions in the reducer.

Wait… that’s it? Yes that’s it! If you wanted to use this in another component all you would have to do is add const { state, dispatch } = useContext(CounterContext) .

In part 2 (coming soon!), we’re going to see how to implement asynchronous tasks / side effects, such as data fetching on mounting and how to fetch again when listening only to a specific state.

Closing Remarks & References

In my personal experience, this is a game changer. State Management is finally readable and straight to the point, no more looking at the export of the file and checking mapStateToProps and mapDispatchToProps and then looking through spaghetti code. Now it’s as simple as subscribing to a context via useContext and using the state and dispatch you want.

Please let me know if anything is unclear, I know I’m not the best at writing and explaining my thoughts. I was just so excited I had to share the good news to everyone 😂

What do you guys think? Would love to hear what you think about this!

If you enjoyed this tutorial please leave a clap(leave 50!) 👏👏👏

Complete Code: https://codesandbox.io/s/kww9rw9rn7

https://reactjs.org/docs/hooks-reference.html#usecontext

Sean Urgel

Written by

Fullstack Javascript Developer at Symph