React with/without Redux

Global Reducer (hook) in React

As you probably know React.js provides us with these two helpful hooks — React.useReducer() and React.useContext(). You may think that combining them in a creative fashion would give you a perfect replacement for Redux? Not so fast :)

First, let’s see what’s the problem with Context-based implementation and how it may look like:

import React from 'react';const initialState = { ... };function reduce(state, action) { ... }const ReducerContext = React.createContext();function useGlobalReducer() {
return React.useContext(ReducerContext);
}
function App() {
const reducer = React.useReducer(reduce, initialState);

return (
<ReducerContext.Provider value={reducer}>
<Example />
</ReducerContext.Provider>
);
}
function Example() {
const [state, dispatch] = useGlobalReducer();
return (...);
}

Can you quickly spot a problem with this approach? Whenever one of your components dispatches a new event, all the connected components, and their subtrees would re-render.

What you want to do instead, is making sure that a component using global state only re-renders if a change detected on a subset of data from the state that it requires.

Here is how a minimalistic solution may look like (src/reducer.js):

import React from 'react';let state = { ... };
const listeners = new Map();
function reduce(state, action) { ... }export function dispatch(action) {
let i = 0;
const prevValues =
Array.from(listeners, ([getValue]) => getValue(state));
// Gets the new state
state = reduce(state, action);
// Notifies subscribed components about the changes
listeners.forEach((setValue, getValue) => {
const value = getValue(state);
if (value !== prevValues[i++) setValue(value);
});
}
export function useState(getValue) {
const [value, setValue] = React.useState(getValue(state));

React.useEffect(() => {
listeners.set(getValue, setValue);
return () => listeners.delete(getValue);
}, [getValue]);
return value;
}
import { dispatch, useState } from './reducer';function ComponentA() {
return (
<button onClick={() => dispatch(...)}>Submit</button>
);
}
function ComponentB() {
const value = useState(state => state.someValue);
return (
<p>Some value: {value}</p>
);
}

Where one component dispatches a global event, and the other one is subscribed to a subset of the global state reacting to the changes.

Note, that in a real-world project most likely you would be using Relay or something like that for global state management, and you would just need a small complementary tool that can be used for things like error messages, snackbar notifications etc.

Live demo https://codesandbox.io/s/react-global-reducer-v0rdn
See also React Starter Kit (serverless edition)

Happy coding!

Bringing the technical edge to early-stage companies with advice on software architecture, best practices, database design, web infrastructure, and DevOps.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store