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

Sean Stuart Urgel
5 min readFeb 10, 2019

--

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.
  2. Knowledge on common state management tools (Redux, Flux, etc.)
  3. React’s Context API (a little) (https://reactjs.org/docs/context.html)

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
  2. Increment / Decrement from <Count />
  3. Fetch again from <SeparateComponent />

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
  2. Create a reducer function that takes a state and action (if you’ve used Redux before this should look familiar)
  3. Use array destructuring to get the state and dispatch object from useReducer , the arguments are self explanatory.
  4. Change the value in CounterContext.Provider to {state, dispatch} so we can pass the shared state and dispatch to all the consumers!

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 Stuart Urgel

Building products with love and intention | Product Engineer @ Casper Studios,