diegohaz/constate

React Context + State

TypeScript

4,006

501 commits

updated Oct 12, 2026

See the code

README

constate logo

Constate

NPM version NPM downloads CI status

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



Basic example

import constate from "constate";
import { useState } from "react";

// 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 constate from "constate";
import { useCallback, useState } from "react";

// 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

pnpm:

pnpm 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 constate from "constate";
import { useState } from "react";

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 constate from "constate";
import { useCallback, useState } from "react";

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 pnpm. Run pnpm install once to install the dependencies and the configured Node.js runtime. Then run pnpm dev to start the examples app, or pnpm test to run the test suite.

License

MIT © Diego Haz

constate
hooks
react
react-context
react-hooks
reactjs
react-state
reakit
state-management

diegohaz/constate

React Context + State

TypeScript

4,006

501 commits

updated Oct 12, 2026

See the code

README

constate logo

Constate

NPM version NPM downloads CI status

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



Basic example

import constate from "constate";
import { useState } from "react";

// 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 constate from "constate";
import { useCallback, useState } from "react";

// 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

pnpm:

pnpm 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 constate from "constate";
import { useState } from "react";

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 constate from "constate";
import { useCallback, useState } from "react";

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 pnpm. Run pnpm install once to install the dependencies and the configured Node.js runtime. Then run pnpm dev to start the examples app, or pnpm test to run the test suite.

License

MIT © Diego Haz

constate
hooks
react
react-context
react-hooks
reactjs
react-state
reakit
state-management

Project health

Overall

7.0/10

Maintained

10/10
OpenSSF Scorecard

Latest release

4.0.0 · May 27, 2026

Releases this year

1

Significant stargazers

(top 24 of 35)

Aron Griffis

81 followers · starred Nov 2020

Vincent Voyer

1,058 followers · starred Sep 2020

rauno

5,236 followers · starred Nov 2018

Kilian Valkhof

544 followers · starred Apr 2018