react-combo-provider
v1.0.8
Published
Tiny (around 0.7Kb minzipped) no dependencies utility function to quickly generate (with 1 call) Contexts, Providers and hooks that share the memory efficiently
Downloads
16
Maintainers
Readme
What
Tiny (around 0.7Kb minzipped) no dependencies utility function to quickly generate (with 1 call) Contexts, Providers and hooks that share the memory efficiently.
Most common scenario is a Store implementation. With Context Provider it's attached to your DOM wherever you want (local or global - you decide).
Why
Defining a Store Context, Provider and hooks (and also Types) requires some boilerplate. We might want the "writing" components to be independent of rendering caused by data changes - then "data" and "API" have to be provided with separate Contexts and hooks (see an awesome article on this topic). Means even more boilerplate.
And if we (for example) have some model and want every field to be changed and rendered independently - it would mean that every field should have its own Context, Provider and hook. The code is pretty standard, but it's a lot of code - see examples below 👇
This simple utility saves you dozens (or even hundreds) of code lines to implement your efficient Contexts/Stores.
How does it work
Every created hook is linked to its dedicated Context under the hood for rendering optimization.
All the generated contexts stack within a single generated ComboProvider wrapper component for convenience. It's easy to forget this fact - that's why generated Provider has a special automatic "...ComboProvider" suffix.
How to use it
You provide (function params)
- provider base name (for the Provider component generation)
- hooks (slices/contexts) names, as many as you want (each hook will be rendered independently due to its individual Context under the hood)
- their shared memory scope (all the data that hooks are going to use, just like a regular hook)
- with custom parameters for more control (will be Provider props, optional)
- return an object that maps every hook to the data you need
You get (function returns)
- controllable Provider component
- your generated hooks
- 🎉 with all the names and types ready to be exported right away
Example 1
Simplest Context providing a value (let's provide the useState result: value + setter, no rendering separation)
import { makeComboProviderAndHooks } from 'react-combo-provider';
import { useState } from 'react';
// Provider component + hooks generated with all the types, use code completion and export them right away
export const { CountStoreComboProvider, useCount } = makeComboProviderAndHooks(
'countStore', // provider base name (<x>ComboProvider component will be generated, with corresponding displayName)
['count'], // a list of hooks/contexts to generate. Every item generates a <x>Context layer for your Provider and a use<X> hook
() => {
// provider's props can be defined as an argument here
return {
// key = Context name (see array above); value = what hook returns (via its own context)
count: useState(0), // value for the useCount hook
};
},
);
If we omit comments - the code is really compact.
import React, {
createContext,
type Dispatch,
type PropsWithChildren,
type ReactElement,
type SetStateAction,
useContext,
useState,
} from 'react';
// define a Context type, makeComboProviderAndHooks infers your types automatically
type Count = [number, Dispatch<SetStateAction<number>>];
// create the Context instance with empty state for Provider check, makeComboProviderAndHooks does it for you
const CountContext = createContext<Count | null>(null);
// displayName - makeComboProviderAndHooks generates it too
CountContext.displayName = 'CountContext';
// Context hook with user-friendly Provider check - makeComboProviderAndHooks generates the same for you
export function useCount(): Count {
const context = useContext(CountContext);
if (!context) {
throw new Error('useCount must be within CountStoreProvider');
}
return context;
}
// Context Provider component with displayName. Also generated by makeComboProviderAndHooks
export function CountStoreProvider({ children }: PropsWithChildren): ReactElement {
return <CountContext.Provider value={useState(0)}>{children}</CountContext.Provider>;
}
As we can see, actually useful code (useState(0) and a few titles) needs a lot of boilerplate around.
Example 2
Same example, but now we want writers to be independent of changed data - 2 Contexts, 2 hooks (useCount, useSetCount)
import { makeComboProviderAndHooks } from 'react-combo-provider';
import { useState } from 'react';
export const { CountStoreComboProvider, useCount, useSetCount } = makeComboProviderAndHooks(
'countStore',
[
'count',
'setCount',
], // 2 contexts/hooks
() => {
const [
count,
setCount,
] = useState(0);
return {
// key = Context name (see array above); value = what hook returns (with its own context)
count, // value for the useCount hook
setCount, // value for the useSetCount hook
};
},
);
We simply added 1 item to the definition array + 1 property to the returned mapping.
Let's see how equivalent code grows up.
import React, {
createContext,
type Dispatch,
type PropsWithChildren,
type ReactElement,
type SetStateAction,
useContext,
useState,
} from 'react';
type CountData = number;
type CountApi = Dispatch<SetStateAction<number>>;
const CountDataContext = createContext<CountData | null>(null);
CountDataContext.displayName = 'CountDataContext';
const CountApiContext = createContext<CountApi | null>(null);
CountApiContext.displayName = 'CountApiContext';
export function useCountData(): CountData {
const context = useContext(CountDataContext);
if (context == null) {
throw new Error('useCountData must be within CountStoreProvider');
}
return context;
}
export function useCountApi(): CountApi {
const context = useContext(CountApiContext);
if (!context) {
throw new Error('useCountApi must be within CountStoreProvider');
}
return context;
}
export function CountStoreProvider({ children }: PropsWithChildren): ReactElement {
const [
count,
setCount,
] = useState(0);
return (
<CountApiContext.Provider value={setCount}>
<CountDataContext.Provider value={count}>{children}</CountDataContext.Provider>
</CountApiContext.Provider>
);
}
Pretend now that we want more fields in our store. And many other stores in our app.
With ComboProvider Context (and Store) creation and rendering optimization have never been easier.