Skip to content

Instantly share code, notes, and snippets.

@ralfstx
Last active February 21, 2021 13:49
Show Gist options
  • Save ralfstx/27048164b88c3951fe324d05d8356bbd to your computer and use it in GitHub Desktop.
Save ralfstx/27048164b88c3951fe324d05d8356bbd to your computer and use it in GitHub Desktop.
Example of a React app with complex state.
/* eslint-disable no-console */
import React, { createContext, useContext, useMemo, useReducer } from 'react';
import ReactDOM from 'react-dom';
// Example of a React app with complex state.
// Use separate context to avoid unnecessary renders.
// ## ContextProvider
const FooContext = createContext();
const BarContext = createContext();
const DispatcherContext = createContext();
/**
* Provides access too the `foo` state.
* Components that use this hook will be rendered only when `foo` changes.
*/
export const useFoo = () => useContext(FooContext);
/**
* Provides access too the `bar` state.
* Components that use this hook will be rendered only when `bar` changes.
*/
export const useBar = () => useContext(BarContext);
/**
* Provides functions to modify state.
* This hook won't trigger render cycles since the dispatch never changes.
*/
export const useDispatcher = () => useContext(DispatcherContext);
// Keep initial state and reducer out of the component to avoid recreation on each render.
const initialState = { foo: 0, bar: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'incrementFoo':
return { foo: state.foo + 1, bar: state.bar };
case 'incrementBar':
return { foo: state.foo, bar: state.bar + 1 };
default:
throw new Error('unknown type');
}
};
export const ContextProvider = ({ children }) => {
console.log('render ContextProvider');
// React guarantees that the `dispatch` function is stable.
const [state, dispatch] = useReducer(reducer, initialState);
// Ensure that the `dispatchers` object doesn't change on each render to keep the
// `DispatcherContext` stable.
const dispatchers = useMemo(
() => ({
incrementFoo: () => dispatch({ type: 'incrementFoo' }),
incrementBar: () => dispatch({ type: 'incrementBar' }),
}),
[dispatch]
);
return (
<FooContext.Provider value={state.foo}>
<BarContext.Provider value={state.bar}>
<DispatcherContext.Provider value={dispatchers}>{children}</DispatcherContext.Provider>
</BarContext.Provider>
</FooContext.Provider>
);
};
// ## Components that use hooks
// Example of a component that will only be rendered when `foo` changes
export const Foo = () => {
console.log('render Foo');
const foo = useFoo();
return <p>Foo: {'#'.repeat(foo)}</p>;
};
// Example of a component that will only be rendered when `bar` changes
export const Bar = () => {
console.log('render Bar');
const bar = useBar();
return <p>Bar: {'#'.repeat(bar)}</p>;
};
// Example of a component that won't be re-rendered
export const Line = () => {
console.log('render Line');
return <hr />;
};
// Example of a component that use the dispatcher hook, won't be re-rendered
export const Buttons = () => {
console.log('render Buttons');
const { incrementFoo, incrementBar } = useDispatcher();
return (
<>
<button onClick={incrementFoo}>Foo</button>
<button onClick={incrementBar}>Bar</button>
</>
);
};
const App = () => {
console.log('render App');
return (
<div>
<ContextProvider>
<Foo />
<Bar />
<Line />
<Buttons />
</ContextProvider>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment