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 |
import React, { useState } from "react";
import createUseContext 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 createUseContext factory
const useCounterContext = createUseContext(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 (
<useCounterContext.Provider>
<Count />
<Button />
</useCounterContext.Provider>
);
}npm:
npm i constateYarn:
yarn add constateConstate exports a single factory method called createUseContext. It receives two arguments: useValue and createMemoInputs (optional). And returns a wrapped hook that can now read state from the Context. The hook also has two static properties: Provider and Context.
It's any custom hook:
import { useState } from "react";
import createUseContext from "constate";
const useCounterContext = createUseContext(() => {
const [count] = useState(0);
return count;
});
console.log(useCounterContext); // React Hook
console.log(useCounterContext.Provider); // React Provider
console.log(useCounterContext.Context); // React Context (if needed)You can receive arguments in the custom hook function. They will be populated with <Provider />:
const useCounterContext = createUseContext(({ initialCount = 0 }) => {
const [count] = useState(initialCount);
return count;
});
function App() {
return (
<useCounterContext.Provider initialCount={10}>
...
</useCounterContext.Provider>
);
}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 Counter() {
const count = useCounterContext();
console.log(count); // 10
}Optionally, you can pass in a function that receives the value returned by useValue and returns an array of inputs. When any input changes, value gets re-evaluated, triggering a re-render on all consumers (components calling useContext()).
If createMemoInputs is undefined, it'll be re-evaluated everytime Provider renders:
// re-render consumers only when value.count changes
const useCounterContext = createUseContext(useCounter, value => [value.count]);
function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return { count, increment };
}This works similarly to the inputs parameter in React.useEffect and other React built-in hooks. In fact, Constate passes it to React.useMemo inputs internally.
You can also achieve the same behavior within the custom hook. This is an equivalent implementation:
import { useMemo } from "react";
const useCounterContext = createUseContext(() => {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
// same as passing `value => [value.count]` to `createMemoInputs` parameter
return useMemo(() => ({ count, increment }), [count]);
});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:start to run examples.
MIT © Diego Haz
