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 constate
Yarn:
yarn add constate
Constate 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