context-with-use-selector
v2.0.0
Published
A custom React context hook that enables efficient state selection and updates with minimal re-renders using a selector function.
Downloads
537
Maintainers
Readme
context-with-use-selector
This library provides a custom context hook that enhances React's context API by enabling efficient state selection through a selector function. With the createContextWithUseSelector
function, you can create a context with a custom useSelectorContext
hook. This hook allows components to subscribe to only specific parts of the context value, ensuring minimal re-renders and optimized performance. This solution is ideal for managing global state in React applications, especially when you need to access and update specific context values without triggering unnecessary re-renders.
Installation
npm install context-with-use-selector
Usage
import { Dispatch, memo, SetStateAction, useEffect, useRef, useState } from "react";
import createContextWithUseSelector from "context-with-use-selector";
// Define the context type
type IContext = {
count1: number;
setCount1: Dispatch<SetStateAction<number>>;
count2: number;
setCount2: Dispatch<SetStateAction<number>>;
count3: number;
setCount3: Dispatch<SetStateAction<number>>;
ref: React.RefObject<HTMLDivElement>;
};
// Create the context with initial values
const { Provider, useSelectorContext } = createContextWithUseSelector<IContext>({} as IContext);
export default function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [count3, setCount3] = useState(0);
const ref = useRef<HTMLDivElement>(null);
return (
<Provider value={{ count1, setCount1, count2, setCount2, count3, setCount3, ref }}>
<Screen />
</Provider>
);
}
const Screen = () => {
const value = useSelectorContext((s) => s.count1);
return (
<>
<p>Value of count1: {value}</p>
<Count1 />
<Count2 />
<Count3 />
</>
);
};
const Count1 = memo(() => {
const { set, val, ref } = useSelectorContext((s) => ({
val: s.count1,
set: s.setCount2,
ref: s.ref,
}));
console.log("Re-render count1");
useEffect(() => {
console.log(ref.current);
}, [ref]);
return (
<div ref={ref}>
value: {val} <br />
<button onClick={() => set((p) => p + 1)}>Change</button>
</div>
);
});
const Count2 = memo(() => {
const { s: { set, val } } = useSelectorContext((s) => ({
s: { val: s.count2, set: s.setCount3 },
}));
console.log("Re-render count2");
return (
<div>
value: {val} <br />
<button onClick={() => set((p) => p + 1)}>Change</button>
</div>
);
});
const Count3 = memo(() => {
const { set, val } = useSelectorContext((s) => ({
val: s.count3,
set: s.setCount1,
}));
console.log("Re-render count3");
return (
<div>
value: {val} <br />
<button onClick={() => set((p) => p + 1)}>Change</button>
</div>
);
});
Explanation
- Provider: Wraps the part of your app that needs access to the context. It accepts a value prop, which is the state you want to share across components.
- useSelectorContext: This hook allows you to subscribe to only the part of the context value that your component needs, reducing unnecessary re-renders. The hook accepts a selector function to specify which part of the context should be accessed.
- Memoization: The
memo
wrapper ensures that the component only re-renders when the specific state it depends on changes, improving performance.
Usage of useSelectorContext
with the Second Parameter
The useSelectorContext
hook accepts a second parameter, comparisonType, which determines how the selected value should be compared to its previous value to decide if the component should re-render. This parameter can be one of three options:
- "shallow": The comparison checks if the two values are the same by reference (using Object.is).
- "deep1": This performs a shallow comparison of all properties in the selected object or array.
- "deepN": This performs a deep comparison of all properties and nested values in the selected object or array.
Using this second parameter can help optimize your app further, allowing you to control the granularity of the comparison. By default, comparisonType
is set to "deepN"
, ensuring that the component re-renders only when the deeply nested values change. However, in cases where performance is a concern, you may opt for "shallow"
or "deep1"
to make comparisons more efficient.
Why Use This Library?
- Efficient Re-Renders: Components only re-render when the selected part of the context value changes. This reduces unnecessary re-renders and optimizes performance.
- Granular State Management: You can select specific pieces of state (e.g., count1, setCount2) without needing to access the entire context, leading to more efficient updates.
- Optimal Performance: By isolating the state each component depends on, this library reduces unnecessary re-renders, improving overall application performance.
Why I created this library:
There is a well-known library called use-context-selector
that provides similar functionality. In the example provided, I used that library, but I noticed it causes unnecessary re-computation of component values during reconciliation. This can result in costly performance issues. To address this problem, I created this library to ensure more efficient state selection and minimize unnecessary reconciliation and re-renders.