React useContextSelector hook with shallow object comparison - BluMintInc fork
npm install @blumintinc/use-context-selector

React useContextSelector hook with shallow object comparison
> BluMintInc Fork: This is an enhanced fork of the original use-context-selector by Daishi Kato.
>
> Key Enhancement: Added shallow object comparison for selected values, preventing unnecessary re-renders when selectors return new objects with identical properties.
The original library uses strict reference equality (Object.is) to determine if selected values have changed. This fork enhances the comparison logic to perform shallow object comparison for plain objects:
- Plain objects: Compares properties one level deep using Object.is for each property
- Primitives, arrays, and other values: Uses Object.is (same as original)
Why this matters:
``javascript
// With the original library, this would cause unnecessary re-renders:
const user = useContextSelector(context, (state) => ({
id: state.user.id,
name: state.user.name
}));
// Each render creates a new object reference, triggering re-renders even when id and name haven't changed
// With this fork, the above pattern works efficiently!
// Re-renders only happen when id or name actually changes
`
This enhancement is particularly useful when:
- Your selectors naturally return plain objects
- You want cleaner selector code without manual memoization
- You're extracting multiple related properties from context
> [!IMPORTANT]
> The goal of this library is to emulate the behavior of the React Context API with Concurrent React.
> Many users try to use this library to avoid re-renders without needing to consider Concurrent React.
> If you simply want to avoid re-renders, we recommend one of the following:
> - Zustand
> - A naive implementation with useSyncExternalStore
> - Experimental react18-use
>
> Learn more
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.
This package requires some peer dependencies, which you need to install by yourself.
`bash`
npm install @blumintinc/use-context-selector react scheduler
Or with yarn:
`bash`
yarn add @blumintinc/use-context-selector react scheduler
Or with pnpm:
`bash`
pnpm add @blumintinc/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.
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.
`javascript
import { useState } from 'react';
import { createRoot } from 'react-dom/client';
import { createContext, useContextSelector } from '@blumintinc/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 (
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 (
const StateProvider = ({ children }) => (
{children}
);
const App = () => (
);
createRoot(document.getElementById('app')).render(
`
This creates a special context for useContextSelector.
#### Parameters
* defaultValue Value
#### Examples
`javascript
import { createContext } from '@blumintinc/use-context-selector';
const PersonContext = createContext({ firstName: '', familyName: '' });
`
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.
For object values, it performs shallow comparison (comparing each property with Object.is).
The selector should return referentially equal result for same input for better performance.
#### Parameters
* context Context\selector
* function (value: Value): Selected
#### Examples
`javascript
import { useContextSelector } from '@blumintinc/use-context-selector';
const firstName = useContextSelector(PersonContext, (state) => state.firstName);
`
This hook returns the entire context value.
Use this instead of React.useContext for consistent behavior.
#### Parameters
* context Context\
#### Examples
`javascript
import { useContext } from '@blumintinc/use-context-selector';
const person = useContext(PersonContext);
`
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
* context Context\
#### Examples
`javascript
import { useContextUpdate } from '@blumintinc/use-context-selector';
const update = useContextUpdate();
// Wrap set state function
update(() => setState(...));
// Experimental suspense mode
update(() => setState(...), { suspense: true });
`
This is a Provider component for bridging multiple react roots
#### Parameters
* $0 {context: Context\
* $0.context $0.value
* $0.children
*
#### Examples
`javascript`
const valueToBridge = useBridgeValue(PersonContext);
return (
{children}
);
This hook return a value for BridgeProvider
#### Parameters
* context Context\
* In order to stop propagation, children of a context provider has to be either created outside of the provider or memoized with React.memo.unstable_batchedUpdates
* Provider trigger re-renders only if the context value is referentially changed.
* Neither context consumers or class components are supported.
* The stale props issue exists in React 17 and below. (Can be resolved with )useContextSelector
* Tearing is only avoided if all consumers get data using . 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)
The examples folder contains working examples.
You can run one of them with
`bash``
PORT=8080 pnpm run examples:01_counter
and open
You can also try them directly:
01
02
03