Easy Use React Context API
React Context: Standard vs. Easy Creation
Standard React Context (Verbose)
The first approach demonstrates the typical, multi-step process for creating and consuming a React Context.
// Step 1: Define the shape of the context
interface CounterContextType {
count: number;
increment: () => void;
decrement: () => void;
}
// Step 2: Create the context
export const CounterContext = React.createContext<
CounterContextType | undefined
>(undefined);
// Step 3: Provide the context
export const CounterProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [count, setCount] = React.useState(0);
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
return (
<CounterContext.Provider value={{ count, increment, decrement }}>
{children}
</CounterContext.Provider>
);
};
// Step 4: Use the context
export function useCounter() {
const context = React.useContext(CounterContext);
if (!context) {
throw new Error("useCounter must be used within a CounterProvider");
}
return context;
}
Pros:
- Explicit and clear about each step of context creation.
- Standard and widely understood React pattern.
Cons:
- Boilerplate: Requires defining the interface, creating the context, creating the provider component, and creating a custom hook for each context. This can become repetitive for multiple contexts.
- Verbosity: A significant amount of code is needed to set up a basic context.
Easy Creation with createReactContext
The second approach introduces a helper function createReactContext to abstract away much of the boilerplate associated with standard React Context.
Helper Function: createReactContext
export function createReactContext<T>(
init: () => T,
name: string,
defaultValue: T | undefined = undefined,
) {
const Context = React.createContext<T | undefined>(defaultValue);
function Provider({ children }: { children: React.ReactNode }) {
const value = init(); // Initialize context value here
return <Context.Provider value={value}>{children}</Context.Provider>;
}
function useEasyContext() {
const context = React.useContext(Context);
if (!context) {
throw new Error(`${name} must be used within a ${name}Provider`);
}
return context;
}
return {
Provider,
use: useEasyContext,
};
}
Use Easy Context
// 🔥 Easy write your business logic - don't care about every time define context
const EasyCounterContext = createReactContext(() => {
const [count, setCount] = React.useState(0);
return {
count,
increment: () => setCount((prev) => prev + 1),
decrement: () => setCount((prev) => prev - 1),
};
}, "CounterContext");
// ✅ provider
<EasyCounterContext.Provider>...</EasyCounterContext.Provider>;
// ✅ use
EasyCounterContext.use;
Pros:
- Reduced Boilerplate: Significantly less code is required to define and use a new context.
- Encapsulation: The createReactContext function encapsulates the common logic for context creation, provider, and hook.
- Focus on Business Logic: Developers can focus directly on the state management logic within the init function, rather than the context plumbing.
- Improved Readability: The usage of EasyCounterContext.Provider and EasyCounterContext.use is more concise and readable.
- Error Handling: Built-in error handling if the context is used outside its provider.
Cons:
- Custom Abstraction: Introduces a custom abstraction layer, which might require a brief understanding of createReactContext initially.
Conclusion
createReactContext<T>(init, name, [defaultValue])
Parameters
- init:
() => T (Required)A function that is executed once when the Provider component renders. This function should contain your state management logic (e.g., useState, useReducer) and return the object that will be the context's value. This value will be memoized automatically. - name:
string (Required)A string representing the name of your context. This name is used in error messages to provide clearer debugging information if the context is used outside of its Provider. For example, if your context is named "UserContext", an error might say: "UserContext must be used within a UserContextProvider". - defaultValue:
T | undefined (Optional)The default value for the context. This value is used when a component tries to consume the context without a Provider in its parent tree. If not provided, the context will default to undefined, and the use hook will throw an error if no provider is found.
Returns An object with the following properties:
- Provider:
React.FC<{ children: React.ReactNode }>A React component that makes the context value available to its children. You wrap the part of your component tree that needs access to the context with this Provider. - use:
() => TA custom React Hook that allows components to subscribe to context changes. When the context value updates, components calling use will re-render. If use is called outside of the Provider, it will throw an informative error.