constate vs use-context-selector
Optimizing React Context State Management and Render Performance
constateuse-context-selector

Optimizing React Context State Management and Render Performance

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
constate03,99520.7 kB9a year agoMIT
use-context-selector02,95043.1 kB62 years agoMIT

Optimizing React Context: Creation Speed vs. Render Performance

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.

🏗️ Setup and API Design

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>;
}

⚡ Re-render Performance

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
}

🧩 Splitting State Logic

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>

🛠️ TypeScript and Developer Experience

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);

📅 Maintenance and Ecosystem

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

📊 Summary: Key Differences

Featureconstateuse-context-selector
Primary GoalReduce boilerplate for Context creationPrevent unnecessary re-renders
Underlying TechStandard React ContextCustom subscription logic
Re-render ControlCoarse (entire context value)Fine-grained (selector based)
Setup StyleHook-based wrapperExplicit Context + Selector
Best ForSpeed of development, simpler appsHigh-performance, complex state

📊 Summary: Key Similarities

Despite their differences, both libraries share common ground in how they integrate with React.

1. ⚛️ Both Rely on React Hooks

  • Both require function components and hooks.
  • Neither works with class components without wrappers.
// Both use useState internally
const [value, setValue] = useState(initial);

2. 🌐 Provider Pattern

  • Both require wrapping parts of your tree with a Provider component.
  • Both follow the standard React composition model.
// Both need a provider wrapper
<Provider>
  <Child />
</Provider>

3. ✅ No External State Store

  • Unlike Redux or MobX, both keep state within React's render cycle.
  • No need for external store configuration or middleware setup.
// State lives in the component tree
// No store.dispatch() calls needed

💡 The Big Picture

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.

How to Choose: constate vs use-context-selector

  • constate:

    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.

  • use-context-selector:

    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.

README for constate

constate logo

Constate

NPM version NPM downloads Size Dependencies GitHub Workflow Status (branch) Coverage Status

Write local state using React Hooks and lift it up to React Context only when needed with minimum effort.


🕹 CodeSandbox demos 🕹
CounterI18nThemingTypeScriptWizard Form

Basic example

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>
  );
}

Learn more

Advanced example

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>
  );
}

Learn more

Installation

npm:

npm i constate

Yarn:

yarn add constate

API

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].

useValue

It'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
}

selectors

Optionally, 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>;
}

Contributing

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.

License

MIT © Diego Haz