React with/without Redux
Konstantin Tarkus
Jul 20 · 2 min read

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!

Konstantin Tarkus

Written by

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

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade