A lightweight React library for creating context with fine-grained selector-based subscriptions, preventing unnecessary re-renders.
- Installation
- Quick Start
- Performance Benefits
- API Reference
- Advanced Usage
- Best Practices
- Common Pitfalls
- TypeScript Support
- Comparison with Other Solutions
- Requirements
- 🎯 Selector-based subscriptions - Components only re-render when their selected values change
- ⚡ Performance optimized - Efficient updates with minimal re-renders
- 🔒 Type-safe - Full TypeScript support with type inference
- 🪶 Lightweight - Minimal dependencies, small bundle size
- 🎨 Simple API - Easy to use with familiar React patterns
- 🛠️ Custom equality functions - Use built-in
shallowor any custom equality function (e.g.,zustand/shallow)
pnpm add @dmrk/use-context-selectorimport { createStore, createUseStore } from '@dmrk/use-context-selector';
// 1. Create your store
const store = createStore({
count: 0,
name: 'John',
});
// 2. Create a custom hook (optional but recommended)
const useStore = createUseStore(store);
// 3. Wrap your app with the Provider
function App() {
return (
<store.Provider>
<Counter />
<Name />
</store.Provider>
);
}
// 4. Use selectors in your components
function Counter() {
const count = useStore(state => state.count);
// ✅ Only re-renders when count changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => store.set({ count: count + 1 })}>Increment</button>
</div>
);
}
function Name() {
const name = useStore(state => state.name);
// ✅ Only re-renders when name changes
return <p>Name: {name}</p>;
}
// When you update count:
store.set({ count: 1 });
// ✅ Only Counter re-renders, Name does not!Note: For non-primitive selectors (objects/arrays), use a custom equality function like shallow or zustand/shallow to prevent unnecessary re-renders.
Creates a new context store with the given initial state.
Parameters:
initialValue: The initial state object
Returns: An object containing:
Provider: React component to wrap your appcontext: The React context (for advanced usage)get(): Function to get current stateset(partial | updater): Function to update state. Accepts either:- A partial state object:
set({ count: 10 }) - An updater function:
set((state) => ({ count: state.count + 1 }))
- A partial state object:
subscribe(): Function to subscribe to state changes (for advanced usage)
Example:
const store = createStore({
user: { name: 'Alice', age: 25 },
theme: 'dark',
});
// Update with object
store.set({ theme: 'light' });
// Update with function (useful when update depends on current state)
store.set(state => ({
user: { ...state.user, age: state.user.age + 1 },
}));Creates a custom hook for the given store with a cleaner API.
Parameters:
store: A store created bycreateStore
Returns: A hook function that accepts:
selector: Function to select a value from stateequalityFn(optional): Custom equality function. Defaults toObject.is. Useshalloworzustand/shallowfor objects/arrays.
Example:
import shallow from '@dmrk/use-context-selector/shallow';
const useStore = createUseStore(store);
function MyComponent() {
const userName = useStore(state => state.user.name);
const user = useStore(state => state.user, shallow);
return <div>{userName}</div>;
}Low-level hook for using context with selectors. Use createUseStore for a better API.
Parameters:
context: The React context from the storeselector: Function to select a value from stateequalityFn(optional): Custom equality function. Defaults toObject.is.
Returns: The selected value
Example:
import shallow from '@dmrk/use-context-selector/shallow';
function MyComponent() {
const count = useStoreSelector(store.context, state => state.count);
const user = useStoreSelector(store.context, state => state.user, shallow);
return <div>{count}</div>;
}Type utility to extract the State type from a store instance.
import type { InferState } from '@dmrk/use-context-selector';
const store = createStore({ count: 0, name: 'John' });
type StoreState = InferState<typeof store>; // { count: number; name: string }You can compute derived values in your selectors:
const useStore = createUseStore(store);
function DoubledCounter() {
const doubled = useStore(state => state.count * 2);
return <div>Doubled: {doubled}</div>;
}Select nested values or combine multiple values:
const store = createStore({
user: { name: 'Alice', age: 25 },
settings: { theme: 'dark', language: 'en' },
});
const useStore = createUseStore(store);
function UserInfo() {
const userAge = useStore(state => state.user.age);
const theme = useStore(state => state.settings.theme);
return <div className={theme}>User age: {userAge}</div>;
}The set method accepts either a partial state object or an updater function:
// Partial updates
store.set({ count: 10 });
// Function form (recommended when update depends on current state)
store.set(state => ({ count: state.count + 1 }));
// Update from component
function UpdateButton() {
const count = useStore(state => state.count);
return (
<button onClick={() => store.set(state => ({ count: state.count + 1 }))}>
Increment
</button>
);
}Access state directly without subscribing:
// Get current state
const currentState = store.get();
// Useful for event handlers or effects
function handleClick() {
const current = store.get();
console.log('Current count:', current.count);
store.set({ count: current.count + 1 });
}- Use equality functions for non-primitive selectors: When selecting objects or arrays, use
shalloworzustand/shallowto prevent unnecessary re-renders. - Select specific values when possible: Prefer selecting individual fields over entire objects when you only need a few values.
- Returning new objects/arrays without equality function: Selectors that return new references cause unnecessary re-renders. Use
shallowor select individual values.
Full type inference and type safety:
const store = createStore({
user: { name: 'Alice', age: 25 },
count: 0,
});
const useStore = createUseStore(store);
function MyComponent() {
// ✅ TypeScript infers return types
const userName = useStore(state => state.user.name); // string
const count = useStore(state => state.count); // number
// ❌ TypeScript error - invalid property
// const invalid = useStore(state => state.invalid);
return <div>{userName}</div>;
}Use InferState<T> to extract the state type from a store instance.
Standard Context:
- ❌ All consumers re-render on any state change
- ❌ Requires manual optimization with useMemo/memo
- ✅ Built into React
use-context-selector:
- ✅ Only re-renders when selected values change
- ✅ Automatic optimization
- ✅ Simpler API for complex state
Zustand:
- ✅ More features (middleware, devtools, etc.)
- ✅ Can be used outside React
- ❌ Larger bundle size
- ❌ More complex API
use-context-selector:
- ✅ Smaller bundle size
- ✅ Simpler API
- ✅ Better for React-only projects
- ✅ Uses React Context (familiar pattern)
- ❌ Fewer features (for now)
- React 18.0.0 or higher
ISC
Contributions are welcome! Please feel free to submit a Pull Request.
dmrk