
ReactJS
Advanced provider patterns enable building complex, composable state management systems. Understanding provider composition, stacking strategies, and higher-order component patterns allows you to scale Context to enterprise applications.
Provider composition involves strategically organizing multiple Providers to create clear, maintainable hierarchies. Proper stacking prevents deep nesting while maintaining clear data flow.
import { ReactNode } from "react";
// Individual providers
function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState(null);
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
}
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Problem: Deep nesting
function App() {
return (
<AuthProvider>
<ThemeProvider>
<UIProvider>
<NotificationProvider>
<MainApp />
</NotificationProvider>
</UIProvider>
</ThemeProvider>
</AuthProvider>
);
}
// Solution: Provider composition
function AppProviders({ children }: { children: ReactNode }) {
return (
<AuthProvider>
<ThemeProvider>
<UIProvider>
<NotificationProvider>{children}</NotificationProvider>
</UIProvider>
</ThemeProvider>
</AuthProvider>
);
}
function App() {
return (
<AppProviders>
<MainApp />
</AppProviders>
);
}
// Better: Composable provider factory
type ProviderComponent = (props: { children: ReactNode }) => JSX.Element;
function composeProviders(...providers: ProviderComponent[]) {
return ({ children }: { children: ReactNode }) => {
return providers.reduceRight(
(acc, Provider) => <Provider>{acc}</Provider>,
children as JSX.Element
);
};
}
const AllProviders = composeProviders(
AuthProvider,
ThemeProvider,
UIProvider,
NotificationProvider
);
function App() {
return (
<AllProviders>
<MainApp />
</AllProviders>
);
}HOCs are functions that take a component and return a new component with additional functionality. They're powerful for injecting Context data:
import { ComponentType } from "react";
// Generic HOC to inject context values
function withContextValue<T,>(
ContextModule: { useModule: () => T },
selector: (value: T) => any
) {
return function WithContextValueComponent<P,>(
Component: ComponentType<P & { contextValue: ReturnType<typeof selector> }>
) {
return function ContextValueComponent(props: P) {
const value = ContextModule.useModule();
const selected = selector(value);
return <Component {...props} contextValue={selected} />;
};
};
}
// Example: Injecting user from AuthContext
interface UserProps {
contextValue: { id: string; email: string };
}
function UserInfo({ contextValue }: UserProps) {
return <div>User: {contextValue.email}</div>;
}
// Create enhanced component
const UserInfoWithAuth = withContextValue(
AuthContext,
(auth) => auth.user
)(UserInfo);
// Multiple HOCs
function withTheme<P,>(Component: ComponentType<P & { theme: string }>) {
return function ThemedComponent(props: P) {
const { theme } = ThemeContext.useModule();
return <Component {...props} theme={theme} />;
};
}
// Compose HOCs
const EnhancedComponent = withTheme(
withContextValue(AuthContext, (auth) => auth.user)(Component)
);
// Alternative: Using render props pattern
function WithContext<T, P>({
context,
selector,
children,
}: {
context: { useModule: () => T };
selector: (value: T) => any;
children: (selected: any) => ReactNode;
}) {
const value = context.useModule();
const selected = selector(value);
return <>{children(selected)}</>;
}
function MyComponent() {
return (
<WithContext
context={AuthContext}
selector={(auth) => auth.user}
>
{(user) => <div>User: {user?.email}</div>}
</WithContext>
);
}<details>
<summary>📚 More Examples</summary>
// Example 1: Provider with dependencies
interface ProviderConfig {
apiUrl: string;
timeout: number;
}
function createAPIProvider(config: ProviderConfig) {
const APIContext = createContext<{
fetch: (path: string) => Promise<any>;
} | undefined>(undefined);
return {
Provider({ children }: { children: ReactNode }) {
const fetch = async (path: string) => {
const response = await globalFetch(`${config.apiUrl}${path}`, {
signal: AbortSignal.timeout(config.timeout),
});
return response.json();
};
return (
<APIContext.Provider value={{ fetch }}>
{children}
</APIContext.Provider>
);
},
useAPI() {
const context = useContext(APIContext);
if (!context) throw new Error("useAPI outside provider");
return context;
},
};
}
// Usage
const APIProvider = createAPIProvider({
apiUrl: "https://api.example.com",
timeout: 5000,
});
function App() {
return <APIProvider.Provider>{/* ... */}</APIProvider.Provider>;
}
// Example 2: Conditional provider wrapping
function SmartProvider({ children, features }: {
children: ReactNode;
features: {
enableAuth?: boolean;
enableTheme?: boolean;
enableAnalytics?: boolean;
};
}) {
let result = children;
if (features.enableAuth) {
result = <AuthProvider>{result}</AuthProvider>;
}
if (features.enableTheme) {
result = <ThemeProvider>{result}</ThemeProvider>;
}
if (features.enableAnalytics) {
result = <AnalyticsProvider>{result}</AnalyticsProvider>;
}
return <>{result}</>;
}
// Usage: only enable providers you need
function App() {
return (
<SmartProvider
features={{
enableAuth: true,
enableTheme: true,
enableAnalytics: false,
}}
>
<MainApp />
</SmartProvider>
);
}
// Example 3: Provider with initialization
async function createAppState() {
const user = await fetchUser();
const preferences = await fetchPreferences();
return { user, preferences };
}
function AsyncProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState<AppState | null>(null);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
createAppState()
.then(setState)
.catch(setError);
}, []);
if (error) return <div>Error: {error.message}</div>;
if (!state) return <div>Loading...</div>;
return (
<AppContext.Provider value={state}>
{children}
</AppContext.Provider>
);
}</details>
A production-grade provider system for large applications:
// providers/index.tsx
import { ReactNode } from "react";
// Separate concerns into specific providers
function AppShellProvider({ children }: { children: ReactNode }) {
return <AuthProvider>{children}</AuthProvider>;
}
function DataProvider({ children }: { children: ReactNode }) {
return (
<UserDataProvider>
<PreferencesProvider>{children}</PreferencesProvider>
</UserDataProvider>
);
}
function UIProvider_Component({ children }: { children: ReactNode }) {
return (
<ThemeProvider>
<ModalProvider>
<NotificationProvider>{children}</NotificationProvider>
</ModalProvider>
</ThemeProvider>
);
}
// Root provider composition
export function RootProvider({ children }: { children: ReactNode }) {
return (
<AppShellProvider>
<DataProvider>
<UIProvider_Component>{children}</UIProvider_Component>
</DataProvider>
</AppShellProvider>
);
}
// app.tsx
import { RootProvider } from "./providers";
export default function App() {
return (
<RootProvider>
<MainApp />
</RootProvider>
);
}
// Advanced: Provider with performance optimization
function OptimizedProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState(initialState);
// Only recreate value when state actually changes
const value = useMemo(
() => ({
...state,
actions: {
updateUser: (user: User) => setState((s) => ({ ...s, user })),
updatePreferences: (prefs: Preferences) =>
setState((s) => ({ ...s, preferences: prefs })),
},
}),
[state]
);
return (
<AppContext.Provider value={value}>{children}</AppContext.Provider>
);
}
// Provider with error boundary
function SafeProvider({ children }: { children: ReactNode }) {
const [error, setError] = useState<Error | null>(null);
if (error) {
return (
<div>
<h1>Context Error</h1>
<p>{error.message}</p>
</div>
);
}
return (
<ErrorBoundary onError={setError}>
<AppProvider>{children}</AppProvider>
</ErrorBoundary>
);
}| Pattern | Use Case | Benefit |
|---|---|---|
| Simple stacking | Few providers | Clear hierarchy |
| Composition | Many providers | Reduces nesting |
| HOC pattern | Component injection | Reusable logic |
| Conditional | Feature flags | Flexible features |
| Async init | Data loading | Initialization handling |
Ready to practice? Challenges | Next: useContext Deep Dive
Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward