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.
useContextSelector is recently proposed.
While waiting for the process, this library provides the API in userland.
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'));
Technical memo
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.
API
createContext
This creates a special context for useContextSelector
.
Parameters
Examples
const PersonContext = createContext({ firstName: '', familyName: '' });
Returns React.Context
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.
Parameters
Examples
const firstName = useContextSelector(PersonContext, state => state.firstName);
Returns any
useContext
This hook returns the entire context value.
Use this instead of React.useContext for consistent behavior.
Parameters
Examples
const person = useContext(PersonContext);
Returns any
Limitations
- Subscriptions are per-context basis. So, even if there are multiple context providers in a component tree, all components are subscribed to all providers. This may lead false positives (extra re-renders).
- 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.
- Context consumers are not supported.
- The stale props issue can't be solved in userland. (workaround with try-catch)
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