use-context-selection
v1.5.3
Published
Improved hook to select partial data from your Context and get updates on your components only when that specific piece of data changes.
Downloads
49
Maintainers
Readme
use-context-selection
IMPORTANT NOTE!
This library won't work as expected for
React >= 18
, as theunstable_changedBits
property fromcreateContext
is no longer supported.The recommendation is to migrate to use-context-selector library instead, which uses a different approach for scheduling updates when a part of your whole state gets updated.
Install
npm install --save use-context-selection
What?
use-context-selection
allows your components to select partial data from Context and receive updates only when that part of your Context value changes.
Why?
By using useContext
hook your components subscribes to the entire Context value and will receive updates anytime one of the values in the store changes.
As an example, let's suppose you have data a
, b
and c
stored in your context, and 3 components A
, B
and C
which respectively access that data. Then, suppose that a
value in the Context is modified; this will trigger a re-render on the tree components (A
, B
and C
).
This might not be an issue if your application only connects to the Context from a small number of components, but things can get slowish for other applications which connect a lot of components with the store.
To overcome this issue, useContextSelection
let you subscribe to any piece of data on your Context and dispatch updates to your components only when it's related data changes! This means, in the example above, changes on a
will result in ONLY component A
to be re-rendered.
How?
This library makes use of a non-documented feature available on React.createContext
API which allows us to disable dispatching updates to every component accessing a Context value. Then, thanks to hooks, we can dispatch updates specifically to the components listening for some changes.
When useContextSelection
is used on a component it will register this component as a listener of the Context; then, when a Context value is updated it will detect the changed data and dispatch an update to the components listening for the specific piece of data.
useContextSelection
receives a getter function as argument to get whatever you want from the Context; e.g.:
// Let's suppose this is the current state of your Context data
const state = {
a: 'A value',
b: 'B value',
c: 'B value',
}
// Then, in component `A` you can select (and listen) only `a` value
const a = useContextSelection(state => state.a);
And that's it! Now, every time state.a
changes then component A
will get re-rendered.
NOTE: you can return anything on your getter function, so for example this is also valid:
const { a, b } = useContextSelection(state => ({ a: state.a, b: state.b });
or even this:
const [ a, b ] = useContextSelection(state => [state.a, state.b]);
Usage
First, you need to create a new Context
using the createContext
function included in this library (using React.createContext
will throw an error).
import { createContext, useContextSelection } from 'use-context-selection';
const AuthContext = createContext({});
Then, you can use Context
as you would normally do on your application; here is just an example but you can implement your provider as you want (e.g. using React.useReducer
):
const AuthProvider = ({ children }) => {
const [user, setUser] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
function loginFn(username) {
setIsLoading(true);
setTimeout(() => {
setUser({ username });
setIsLoading(false);
}, 1000);
}
const contextValue = {
user,
loginFn,
isLoading,
isAuthenticated: Boolean(user),
};
return (
<AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
);
}
Now, from you component you can listen for specific parts of the state:
const AppContent = () => {
const { isLoading, loginFn } = useContextSelection(AuthContext, state => ({
isLoading: state.isLoading,
loginFn: state.loginFn,
}));
if (isLoading) {
return 'Loading...';
}
return <LoginForm loginFn={loginFn} />;
}
Finally, remember to wrap your application with the Provider.
export default App = () => {
<AuthProvider>
<AppContent />
</AuthProvider>
}
Or you can also use a selection function using Context.Consumer
component in this way:
const App = () => (
<AuthProvider>
<AuthContext.Consumer selection={state => ({ isLoading: state.isLoading, loginFn: state.loginFn })}>
{({ isLoading, loginFn }) => {
if (isLoading) {
return 'Loading...';
}
return <LoginForm loginFn={loginFn} />;
}}
</AuthContext.Consumer>
</AuthProvider>
);
Demo App
Check performance comparison against useContext
hook on the following app-example:
https://edriang.github.io/use-context-selection/
API
createContext
Creates a smart Context
object which compares changes on your Context state and dispatches changes to subscribers.
| Param | Type | Description | Optional / Required | |-------|-------------|-------------|---------------| | initValue | any | Initial value for the Context | Required | | equalityFn | Function | Function used to compare old vs new state; by default it performs shallow equality check | Optional |
- Return Value: Context
useContextSelection
Hook to access your Context state; receives a Context
object created with createContext
function and a selection
function.
This Hook will trigger a re-render on your component every-time these conditions are met:
- The state on your
Context
changes - The
selection
function returns a different value since the last time it rendered
| Param | Type | Description | Optional / Required |
|-------|-------------|-------------|---------------|
| Context | Context | Context created with createContext
function from this library | Required |
| selection | Function | Use this selection function to retrieve data from your Context; receives the current state / value and should return whatever your component needs | Required |
- Return Value: any; whatever you are returning on
selection
function.
isEqualShallow
This is the default comparator function used internally if equalityFn
param is not provided to createContext
.
This function is exported as part of the library in case you need it as foundations for your own equality check function.
You need to remember two things about this default equality function:
- As the name already implies, it performs a shallow equality check for performance reassons;
- It will ignore comparing
functions
; this comes handy as you'd probably include in your store functions to mutate the current state; this way there is no need to memoize the functions (e.g. usingReact.useCallback
).
| Param | Type | Description | Optional / Required | |-------|-------------|-------------|---------------| | newState | any | New state to compare with | Required | | oldState | any | Old state to compare with | Required |
- Return Value: boolean; whether both states are considered the same or not.
Related projects
This library is used internally by react-connect-context-hooks, a library for easily managing application-state.
Disclaimer
This library was inspired by use-context-selector, but here are some key differences:
use-context-selection
allows you to specify a customequality-check
function.- Internally,
use-context-selection
manages different set of listeners per Context, whileuse-context-selector
stores all listeners (even for different Contexts) within a single shared Set. use-context-selection
allows you to use an enhanced version ofContext.Consumer
component, which supportsselection
functions likeuseContextSelection
hook.use-context-selector
executes different logic when running inproduction
mode than while indevelopment
. In production mode it'll trigger updates on listener components during therender
phase; this behavior is something React would complain about with some scary warning messages on the console if it runs ondevelopment
mode.
That being said, based on some internal testing, both libraries are seamlessly performant.
License
MIT © edriang