React useContextSelector hook in userland
React Context and useContext is often used to avoid prop drilling, however it's known that there's a performance issue. When a context value is changed, all components that useContext will re-render.
useContextSelector is proposed. While waiting for the process, this library provides the API in userland.
npm install use-context-selector
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { createContext, useContextSelector } from 'use-context-selector';
const context = createContext(null);
const Counter1 = () => {
const count1 = useContextSelector(context, v => v[0].count1);
const setState = useContextSelector(context, v => v[1]);
const increment = () => setState(s => ({
...s,
count1: s.count1 + 1,
}));
return (
<div>
<span>Count1: {count1}</span>
<button type="button" onClick={increment}>+1</button>
{Math.random()}
</div>
);
};
const Counter2 = () => {
const count2 = useContextSelector(context, v => v[0].count2);
const setState = useContextSelector(context, v => v[1]);
const increment = () => setState(s => ({
...s,
count2: s.count2 + 1,
}));
return (
<div>
<span>Count2: {count2}</span>
<button type="button" onClick={increment}>+1</button>
{Math.random()}
</div>
);
};
const StateProvider = ({ children }) => {
const [state, setState] = useState({ count1: 0, count2: 0 });
return (
<context.Provider value={[state, setState]}>
{children}
</context.Provider>
);
};
const App = () => (
<StateProvider>
<Counter1 />
<Counter2 />
</StateProvider>
);
ReactDOM.render(<App />, document.getElementById('app'));
React context by nature triggers propagation of component re-rendering
if a value is changed. To avoid this, this library uses undocumented
feature of calculateChangedBits
. It then uses a subscription model
to force update when a component needs to re-render.
This creates a special context for useContextSelector
.
defaultValue
any
const PersonContext = createContext({ firstName: '', familyName: '' });
Returns React.Context
This hook returns context selected value by selector.
It will only accept context created by createContext
.
It will trigger re-render if only the selected value is referentially changed.
context
React.Contextselector
Function
const firstName = useContextSelector(PersonContext, state => state.firstName);
Returns any
This hook returns the entire context value. Use this instead of React.useContext for consistent behavior.
context
React.Context
const person = useContext(PersonContext);
Returns any
This hook returns an update function that accepts a thunk function
Use this for a function that will change a value.
context
import { useContextUpdate } from 'use-context-selector';
const update = useContextUpdate();
update(() => setState(...));
This is a Provider component for bridging multiple react roots
props
Objectprops.context
React.Contextprops.value
anyprops.children
React.ReactNote
const valueToBridge = useBridgeValue(PersonContext);
return (
<Renderer>
<BridgeProvider context={PersonContext} value={valueToBridge}>
{children}
</BridgeProvider>
</Renderer>
);
Returns React.ReactElement
This hook return a value for BridgeProvider
context
React.Context
Returns any
- In order to stop propagation,
children
of a context provider has to be either created outside of the provider or memoized withReact.memo
. - Provider trigger re-renders only if the context value is referentially changed.
- Context consumers are not supported.
- The stale props issue can't be solved in userland. (workaround with try-catch)
The examples folder contains working examples. You can run one of them with
PORT=8080 npm run examples:01_minimal
and open http://localhost:8080 in your web browser.