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;
}

Usage example

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.

Love podcasts or audiobooks? Learn on the go with our new app.

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
Konstantin Tarkus

Konstantin Tarkus

1.6K Followers

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