React with/without Redux

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!