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 useContext with selector support 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.
v1 uses calculateChangedBits=()=>0
technique to stop propagation,
while v2 uses useMutableSource
.
Install
npm install use-context-selector
Usage
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'));
Migrating from v1 to v2
In v1:
useContextSelector(context, state => state.count);
In v2:
useContext(context, useCallback(state => state.count, []));
In this case, you can (should) also define the selector function outside render.
API
createContext
This creates a special context for selector-enabled useContext
.
It doesn't pass its value but a ref of the value.
Unlike the original context provider, this context provider
expects the context value to be immutable and stable.
Parameters
Examples
import { createContext } from 'use-context-selector';
const PersonContext = createContext({ firstName: '', familyName: '' });
useContext
This hook returns context value with optional selector.
It will only accept context created by createContext
.
It will trigger re-render if only the selected value is referentially changed.
The selector must be stable.
Either define selector outside render or wrap with useCallback
.
The selector should return referentially equal result for same input for better performance.
Parameters
context
Context<Value>selector
function (value: Value): Selected (optional, default identity as(value:Value)=>Selected
)
Examples
import { useContext } from 'use-context-selector';
const firstName = useContext(PersonContext, state => state.firstName);
wrapCallbackWithPriority
A utility function to wrap a callback function with higher priority
Use this for a callback that will change a value,
which will be fed into context provider.
Parameters
Examples
import { wrapCallbackWithPriority } from 'use-context-selector';
const wrappedCallback = wrapCallbackWithPriority(callback);
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 isn't solved.
- Due to the current useMutableSource limitation, a selector can't return a function. (See
01_basic_spec
wrapping setState) - Tearing is only avoided within the Provider tree. A value outside the Provider will tear. (
02_tearing_spec
fails)
Examples
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.
You can also try them in codesandbox.io:
01
02
Related projects