Last active
February 21, 2021 13:49
-
-
Save ralfstx/27048164b88c3951fe324d05d8356bbd to your computer and use it in GitHub Desktop.
Example of a React app with complex state.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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