constate and use-context-selector are both utilities designed to improve how developers handle shared state in React applications using the Context API. constate focuses on simplifying the creation of context providers by wrapping custom hooks, reducing boilerplate code significantly. use-context-selector addresses a specific performance bottleneck in React Context by allowing components to subscribe to only specific parts of the context value, preventing unnecessary re-renders when unrelated state changes.
Both constate and use-context-selector aim to make React Context easier to work with, but they solve different problems. constate streamlines how you build and expose context, while use-context-selector optimizes how components read that context. Understanding this distinction is key to picking the right tool for your architecture.
constate wraps a custom hook to automatically generate a Provider and a consumer hook. You write normal React hooks logic, and the library handles the Context creation behind the scenes.
// constate: Create context from a hook
import { createState } from 'constate';
const [CountProvider, useCount] = createState(() => {
const [count, setCount] = useState(0);
const increment = () => setCount(c => c + 1);
return { count, increment };
});
// Usage in component
function Counter() {
const { count, increment } = useCount();
return <button onClick={increment}>{count}</button>;
}
use-context-selector requires you to define a context explicitly and then use a special selector hook to read values. This adds a small amount of setup but grants control over subscriptions.
// use-context-selector: Define context and select slices
import { createContext, useContextSelector } from 'use-context-selector';
const CountContext = createContext();
function CountProvider({ children }) {
const [count, setCount] = useState(0);
const value = { count, setCount };
return <CountContext.Provider value={value}>{children}</CountContext.Provider>;
}
// Usage in component
function Counter() {
const count = useContextSelector(CountContext, v => v.count);
const setCount = useContextSelector(CountContext, v => v.setCount);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
This is the most critical technical difference. Standard React Context triggers all consumers when any part of the value changes. constate uses standard Context under the hood, while use-context-selector intercepts this behavior.
constate will re-render all components using useCount if the object returned by the hook changes reference, even if they only use one property. You often need to split contexts manually to avoid this.
// constate: All consumers re-render on any change
function Display() {
const { count } = useCount(); // Re-renders even if only 'increment' changes
return <div>{count}</div>;
}
use-context-selector allows components to subscribe to specific fields. If count changes, a component selecting only setCount will not re-render. This is vital for large trees.
// use-context-selector: Only re-renders if 'count' changes
function Display() {
const count = useContextSelector(CountContext, v => v.count);
return <div>{count}</div>; // Safe from unrelated updates
}
When your state grows, managing it becomes harder. Both libraries handle this differently.
constate encourages splitting logic into multiple small hooks and providers. This keeps files small but increases the number of providers in your tree.
// constate: Multiple providers for separation
const [UserProvider, useUser] = createState(userHook);
const [SettingsProvider, useSettings] = createState(settingsHook);
// App wrapper
<UserProvider>
<SettingsProvider>
<App />
</SettingsProvider>
</UserProvider>
use-context-selector allows you to keep one large context object but select slices safely. This reduces provider nesting but requires careful selector management to avoid performance traps.
// use-context-selector: Single provider, multiple selectors
const AppContext = createContext();
// App wrapper
<AppContext.Provider value={{ user, settings, actions }}>
<App />
</AppContext.Provider>
Type safety is crucial for maintainable codebases. Both libraries support TypeScript well, but the experience differs.
constate infers types directly from your hook return value. This feels very natural and requires minimal extra code.
// constate: Types inferred automatically
const [Provider, useValue] = createState(() => {
const [count, setCount] = useState<number>(0);
return { count, setCount }; // Types derived here
});
use-context-selector often requires explicit type definitions for the context value to ensure selectors are typed correctly, especially when the default value is null or undefined.
// use-context-selector: Explicit types often needed
type ContextType = { count: number; setCount: (v: number) => void };
const Context = createContext<ContextType | null>(null);
// Selector needs to handle potential null or use non-null assertion
const count = useContextSelector(Context, v => v?.count ?? 0);
Long-term support matters for production apps.
constate is a stable library but sees infrequent updates. It is considered feature-complete, but if React changes Context internals significantly, it might lag in adaptation. It is not officially deprecated, but activity is low.
use-context-selector is maintained by Daishi Kato, who also maintains Zustand. It receives updates to stay compatible with React concurrent features and is widely used in the performance-focused community.
// Both work in standard React environments
// However, use-context-selector has specific notes for React 18+ batching
// constates relies on standard React behavior
| Feature | constate | use-context-selector |
|---|---|---|
| Primary Goal | Reduce boilerplate for Context creation | Prevent unnecessary re-renders |
| Underlying Tech | Standard React Context | Custom subscription logic |
| Re-render Control | Coarse (entire context value) | Fine-grained (selector based) |
| Setup Style | Hook-based wrapper | Explicit Context + Selector |
| Best For | Speed of development, simpler apps | High-performance, complex state |
Despite their differences, both libraries share common ground in how they integrate with React.
// Both use useState internally
const [value, setValue] = useState(initial);
// Both need a provider wrapper
<Provider>
<Child />
</Provider>
// State lives in the component tree
// No store.dispatch() calls needed
constate is like a shortcut for writing Context code. It removes the repetitive setup so you can focus on logic. Use it when your app is small enough that Context performance limits won't hurt you.
use-context-selector is like a performance patch for Context. It lets you keep the Context API you know while fixing its biggest flaw — over-rendering. Use it when you need scale without moving to a completely different state library.
Final Thought: If you are building a new app and expect it to grow complex, use-context-selector offers more headroom. If you need to ship a feature quickly and state is simple, constate gets you there faster.
Choose constate if you want to quickly scaffold context providers with minimal boilerplate and your application does not suffer from performance issues due to frequent context updates. It is ideal for small to medium-sized projects where developer speed and clean hook-based APIs are more critical than fine-grained render optimization.
Choose use-context-selector if your application has large context objects that update frequently, causing performance drops due to unnecessary re-renders in child components. It is the better choice for complex dashboards or data-heavy apps where you need the simplicity of Context but the performance characteristics of a selector-based state library.
Write local state using React Hooks and lift it up to React Context only when needed with minimum effort.
| Counter | I18n | Theming | TypeScript | Wizard Form |
import React, { useState } from "react";
import constate from "constate";
// 1️⃣ Create a custom hook as usual
function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount(prevCount => prevCount + 1);
return { count, increment };
}
// 2️⃣ Wrap your hook with the constate factory
const [CounterProvider, useCounterContext] = constate(useCounter);
function Button() {
// 3️⃣ Use context instead of custom hook
const { increment } = useCounterContext();
return <button onClick={increment}>+</button>;
}
function Count() {
// 4️⃣ Use context in other components
const { count } = useCounterContext();
return <span>{count}</span>;
}
function App() {
// 5️⃣ Wrap your components with Provider
return (
<CounterProvider>
<Count />
<Button />
</CounterProvider>
);
}
import React, { useState, useCallback } from "react";
import constate from "constate";
// 1️⃣ Create a custom hook that receives props
function useCounter({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount);
// 2️⃣ Wrap your updaters with useCallback or use dispatch from useReducer
const increment = useCallback(() => setCount(prev => prev + 1), []);
return { count, increment };
}
// 3️⃣ Wrap your hook with the constate factory splitting the values
const [CounterProvider, useCount, useIncrement] = constate(
useCounter,
value => value.count, // becomes useCount
value => value.increment // becomes useIncrement
);
function Button() {
// 4️⃣ Use the updater context that will never trigger a re-render
const increment = useIncrement();
return <button onClick={increment}>+</button>;
}
function Count() {
// 5️⃣ Use the state context in other components
const count = useCount();
return <span>{count}</span>;
}
function App() {
// 6️⃣ Wrap your components with Provider passing props to your hook
return (
<CounterProvider initialCount={10}>
<Count />
<Button />
</CounterProvider>
);
}
npm:
npm i constate
Yarn:
yarn add constate
constate(useValue[, ...selectors])Constate exports a single factory method. As parameters, it receives useValue and optional selector functions. It returns a tuple of [Provider, ...hooks].
useValueIt's any custom hook:
import { useState } from "react";
import constate from "constate";
const [CountProvider, useCountContext] = constate(() => {
const [count] = useState(0);
return count;
});
You can receive props in the custom hook function. They will be populated with <Provider />:
const [CountProvider, useCountContext] = constate(({ initialCount = 0 }) => {
const [count] = useState(initialCount);
return count;
});
function App() {
return (
<CountProvider initialCount={10}>
...
</CountProvider>
);
}
The API of the containerized hook returns the same value(s) as the original, as long as it is a descendant of the Provider:
function Count() {
const count = useCountContext();
console.log(count); // 10
}
selectorsOptionally, you can pass in one or more functions to split the custom hook value into multiple React Contexts. This is useful so you can avoid unnecessary re-renders on components that only depend on a part of the state.
A selector function receives the value returned by useValue and returns the value that will be held by that particular Context.
import React, { useState, useCallback } from "react";
import constate from "constate";
function useCounter() {
const [count, setCount] = useState(0);
// increment's reference identity will never change
const increment = useCallback(() => setCount(prev => prev + 1), []);
return { count, increment };
}
const [Provider, useCount, useIncrement] = constate(
useCounter,
value => value.count, // becomes useCount
value => value.increment // becomes useIncrement
);
function Button() {
// since increment never changes, this will never trigger a re-render
const increment = useIncrement();
return <button onClick={increment}>+</button>;
}
function Count() {
const count = useCount();
return <span>{count}</span>;
}
If you find a bug, please create an issue providing instructions to reproduce it. It's always very appreciable if you find the time to fix it. In this case, please submit a PR.
If you're a beginner, it'll be a pleasure to help you contribute. You can start by reading the beginner's guide to contributing to a GitHub project.
When working on this codebase, please use yarn. Run yarn examples to run examples.
MIT © Diego Haz