Constate

React Context + State

README

constate logo


Constate


NPM versionNPM downloadsSizeDependenciesGitHub 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 🕹
Counter I18n Theming TypeScript Wizard Form

Basic example


  1. ``` js
  2. import React, { useState } from "react";
  3. import constate from "constate";

  4. // 1️⃣ Create a custom hook as usual
  5. function useCounter() {
  6.   const [count, setCount] = useState(0);
  7.   const increment = () => setCount(prevCount => prevCount + 1);
  8.   return { count, increment };
  9. }

  10. // 2️⃣ Wrap your hook with the constate factory
  11. const [CounterProvider, useCounterContext] = constate(useCounter);

  12. function Button() {
  13.   // 3️⃣ Use context instead of custom hook
  14.   const { increment } = useCounterContext();
  15.   return <button onClick={increment}>+</button>;
  16. }

  17. function Count() {
  18.   // 4️⃣ Use context in other components
  19.   const { count } = useCounterContext();
  20.   return <span>{count}</span>;
  21. }

  22. function App() {
  23.   // 5️⃣ Wrap your components with Provider
  24.   return (
  25.     <CounterProvider>
  26.       <Count />
  27.       <Button />
  28.     </CounterProvider>
  29.   );
  30. }
  31. ```


Advanced example


  1. ``` js
  2. import React, { useState, useCallback } from "react";
  3. import constate from "constate";

  4. // 1️⃣ Create a custom hook that receives props
  5. function useCounter({ initialCount = 0 }) {
  6.   const [count, setCount] = useState(initialCount);
  7.   // 2️⃣ Wrap your updaters with useCallback or use dispatch from useReducer
  8.   const increment = useCallback(() => setCount(prev => prev + 1), []);
  9.   return { count, increment };
  10. }

  11. // 3️⃣ Wrap your hook with the constate factory splitting the values
  12. const [CounterProvider, useCount, useIncrement] = constate(
  13.   useCounter,
  14.   value => value.count, // becomes useCount
  15.   value => value.increment // becomes useIncrement
  16. );

  17. function Button() {
  18.   // 4️⃣ Use the updater context that will never trigger a re-render
  19.   const increment = useIncrement();
  20.   return <button onClick={increment}>+</button>;
  21. }

  22. function Count() {
  23.   // 5️⃣ Use the state context in other components
  24.   const count = useCount();
  25.   return <span>{count}</span>;
  26. }

  27. function App() {
  28.   // 6️⃣ Wrap your components with Provider passing props to your hook
  29.   return (
  30.     <CounterProvider initialCount={10}>
  31.       <Count />
  32.       <Button />
  33.     </CounterProvider>
  34.   );
  35. }
  36. ```


Installation


npm:

  1. ```sh
  2. npm i constate
  3. ```

Yarn:

  1. ```sh
  2. yarn add constate
  3. ```

API


constate(useValue[, ...selectors])


Constate exports a single factory method. As parameters, it receives [useValue](#usevalue) and optional [selector](#selectors) functions. It returns a tuple of [Provider, ...hooks].

useValue


It's any custom hook:

  1. ``` js
  2. import { useState } from "react";
  3. import constate from "constate";

  4. const [CountProvider, useCountContext] = constate(() => {
  5.   const [count] = useState(0);
  6.   return count;
  7. });
  8. ```

You can receive props in the custom hook function. They will be populated with ``:

  1. ``` js
  2. const [CountProvider, useCountContext] = constate(({ initialCount = 0 }) => {
  3.   const [count] = useState(initialCount);
  4.   return count;
  5. });

  6. function App() {
  7.   return (
  8.     <CountProvider initialCount={10}>
  9.       ...
  10.     </CountProvider>
  11.   );
  12. }
  13. ```

The API of the containerized hook returns the same value(s) as the original, as long as it is a descendant of the Provider:

  1. ``` js
  2. function Count() {
  3.   const count = useCountContext();
  4.   console.log(count); // 10
  5. }
  6. ```

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](#usevalue) and returns the value that will be held by that particular Context.

  1. ``` js
  2. import React, { useState, useCallback } from "react";
  3. import constate from "constate";

  4. function useCounter() {
  5.   const [count, setCount] = useState(0);
  6.   // increment's reference identity will never change
  7.   const increment = useCallback(() => setCount(prev => prev + 1), []);
  8.   return { count, increment };
  9. }

  10. const [Provider, useCount, useIncrement] = constate(
  11.   useCounter,
  12.   value => value.count, // becomes useCount
  13.   value => value.increment // becomes useIncrement
  14. );

  15. function Button() {
  16.   // since increment never changes, this will never trigger a re-render
  17.   const increment = useIncrement();
  18.   return <button onClick={increment}>+</button>;
  19. }

  20. function Count() {
  21.   const count = useCount();
  22.   return <span>{count}</span>;
  23. }
  24. ```

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