React Hooks for State Management!(useContext, useEffect, useReducer)
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 â
- 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.
- Knowledge on common state management tools (Redux, Flux, etc.)
- 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
- Fetch count from server
- Increment / Decrement from <Count />
- 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
- import
useReducer
- Create a reducer function that takes a state and action (if youâve used Redux before this should look familiar)
- Use array destructuring to get the
state
anddispatch
object fromuseReducer
, the arguments are self explanatory. - 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