What is use-context-selector?
The `use-context-selector` package is a React hook that allows for more granular context updates by enabling the selection of specific parts of the context. This can help optimize performance by reducing unnecessary re-renders in components that consume context.
What are use-context-selector's main functionalities?
Context Selection
This feature allows you to select a specific part of the context value, which can help in optimizing performance by preventing unnecessary re-renders of components that consume the context.
```jsx
import React, { createContext } from 'react';
import { useContextSelector } from 'use-context-selector';
const MyContext = createContext();
const MyComponent = () => {
const value = useContextSelector(MyContext, (v) => v.someValue);
return <div>{value}</div>;
};
const App = () => {
const contextValue = { someValue: 'Hello, World!' };
return (
<MyContext.Provider value={contextValue}>
<MyComponent />
</MyContext.Provider>
);
};
export default App;
```
Avoiding Unnecessary Re-renders
This example demonstrates how `use-context-selector` can be used to avoid unnecessary re-renders. The `MyComponent` only re-renders when `someValue` changes, not when `otherValue` changes.
```jsx
import React, { createContext, useState } from 'react';
import { useContextSelector } from 'use-context-selector';
const MyContext = createContext();
const MyComponent = () => {
const value = useContextSelector(MyContext, (v) => v.someValue);
console.log('MyComponent re-rendered');
return <div>{value}</div>;
};
const App = () => {
const [contextValue, setContextValue] = useState({ someValue: 'Hello, World!', otherValue: 42 });
return (
<MyContext.Provider value={contextValue}>
<MyComponent />
<button onClick={() => setContextValue({ ...contextValue, otherValue: contextValue.otherValue + 1 })}>
Update Other Value
</button>
</MyContext.Provider>
);
};
export default App;
```
Other packages similar to use-context-selector
react-redux
React-Redux is a popular library for managing state in React applications using the Redux pattern. It provides a `connect` function and hooks like `useSelector` and `useDispatch` to connect React components to the Redux store. While `react-redux` is more comprehensive and suited for global state management, `use-context-selector` is more lightweight and focused on optimizing context usage.
recoil
Recoil is a state management library for React that provides a way to manage shared state with atoms and selectors. It offers fine-grained control over state updates and dependencies, similar to `use-context-selector`. However, Recoil is more feature-rich and provides a more structured approach to state management compared to the more lightweight `use-context-selector`.
zustand
Zustand is a small, fast, and scalable state management solution for React. It uses hooks to manage state and provides a simple API for creating and consuming state. Like `use-context-selector`, it aims to optimize performance by minimizing re-renders, but it is more focused on global state management rather than context-specific optimizations.
use-context-selector
React useContextSelector hook in userland
Introduction
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.
To solve this issue,
useContextSelector
is proposed and later proposed
Speculative Mode
with context selector support.
This library provides the API in userland.
Prior to v1.3, it uses changedBits=0
feature to stop propagation,
v1.3 no longer depends on this undocumented feature.
Install
This package requires some peer dependencies, which you need to install by yourself.
npm install use-context-selector react scheduler
Notes for library authors:
Please do not forget to keep "peerDependencies"
and
note instructions to let users to install peer dependencies.
Technical memo
To make it work like original React context, it uses
useReducer cheat mode intentionally.
It also requires useContextUpdate
to behave better in concurrent rendering.
Its usage is optional and only required if the default behavior is unexpected.
If you need a simpler solution, you can use useSyncExternalStore
without any libraries. See an example.
Usage
import { useState } from 'react';
import { createRoot } from 'react-dom/client';
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 }) => (
<context.Provider value={useState({ count1: 0, count2: 0 })}>
{children}
</context.Provider>
);
const App = () => (
<StateProvider>
<Counter1 />
<Counter2 />
</StateProvider>
);
createRoot(document.getElementById('app')).render(<App />);
API
createContext
This creates a special context for useContextSelector
.
Parameters
Examples
import { createContext } from 'use-context-selector';
const PersonContext = createContext({ firstName: '', familyName: '' });
useContextSelector
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.
The selector should return referentially equal result for same input for better performance.
Parameters
context
Context<Value> selector
function (value: Value): Selected
Examples
import { useContextSelector } from 'use-context-selector';
const firstName = useContextSelector(PersonContext, (state) => state.firstName);
useContext
This hook returns the entire context value.
Use this instead of React.useContext for consistent behavior.
Parameters
Examples
import { useContext } from 'use-context-selector';
const person = useContext(PersonContext);
useContextUpdate
This hook returns an update function to wrap an updating function
Use this for a function that will change a value in
concurrent rendering in React 18.
Otherwise, there's no need to use this hook.
Parameters
Examples
import { useContextUpdate } from 'use-context-selector';
const update = useContextUpdate();
update(() => setState(...));
update(() => setState(...), { suspense: true });
BridgeProvider
This is a Provider component for bridging multiple react roots
Parameters
Examples
const valueToBridge = useBridgeValue(PersonContext);
return (
<Renderer>
<BridgeProvider context={PersonContext} value={valueToBridge}>
{children}
</BridgeProvider>
</Renderer>
);
useBridgeValue
This hook return a value for BridgeProvider
Parameters
Limitations
- In order to stop propagation,
children
of a context provider has to be either created outside of the provider or memoized with React.memo
. - Provider trigger re-renders only if the context value is referentially changed.
- Neither context consumers or class components are supported.
- The stale props issue can't be solved in userland.
- Tearing is only avoided if all consumers get data using
useContextSelector
. If you use both props and use-context-selector
to pass the same data, they may provide inconsistence data for a brief moment. (02_tearing_spec
fails)
Examples
The examples folder contains working examples.
You can run one of them with
PORT=8080 yarn run examples:01_counter
and open http://localhost:8080 in your web browser.
You can also try them in codesandbox.io:
01
02
03
Projects that use use-context-selector