superctx
v0.3.2
Published
An enhanced React Context API with better TypeScript support, error handling, and utilities for multiple contexts.
Downloads
763
Readme
SuperCtx
An ergonomic superset of React's Context API for butter smooth dependency injection 🧈
Warning
This library is meant to be used with the React Compiler. Relying heavily on the Context API without the React Compiler can cause serious performance sinks in your application.
Overview
superctx extends React's Context API with:
- ✅ Better TypeScript inference than native Context API
- ✅ Automatic error handling for missing providers
- ✅ Enhanced base context providers with
addBase - ✅ Easy access to multiple contexts
- ✅ Lazy initialization of default values
- ✅ Zero dependencies (only uses React)
Especially recommended for AI coding agents:
- ✅ Less boilerplate means lower token consumption
- ✅ More compact imports keep prompts and diffs shorter
- ✅ Lower token usage leads to better iteration quality
Requirements
- React 19 or above
Installation
pnpm add superctxUsage
Basic Context Creation
import { createSuperContext } from "superctx";
// Context that requires a provider
const UserContext = createSuperContext<{ name: string; email: string }>({
notProvidedMessage: "User context not provided",
});
// Context with default value
const ThemeContext = createSuperContext<"light" | "dark">({
initialValue: "light",
});
// Context with lazy initialization
const ConfigContext = createSuperContext<AppConfig>({
getInitialValue: () => loadConfigFromStorage(),
});Using Contexts
function UserProfile() {
const user = UserContext.useProvided();
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// Consumer pattern (alternative to hook)
function UserDisplay() {
return <UserContext.Consumer>{(user) => <div>{user.name}</div>}</UserContext.Consumer>;
}Base Components with addBase
Create root components that automatically provide context values:
import { createSuperContext, addBase } from "superctx";
const Environment = addBase(
createSuperContext<NebulyEnvironment>({
notProvidedMessage: "Environment not provided",
}),
(Provider) => {
return ({ children }) => {
const env = getEnvironmentValues();
return <Provider value={env}>{children}</Provider>;
};
},
);
// Usage - just use the Base component
function App() {
return (
<Environment.Base>
<YourApp />
</Environment.Base>
);
}
// Access the context anywhere
function Component() {
const env = Environment.useProvided();
return <div>{env.apiUrl}</div>;
}Working with Multiple Contexts
Use useProviders to access multiple contexts at once:
import { useProviders } from "superctx";
function MyComponent() {
const [user, theme, environment] = useProviders([UserContext, ThemeContext, Environment]);
return (
<div className={theme}>
<h1>{user.name}</h1>
<p>API: {environment.apiUrl}</p>
</div>
);
}Consumer Component
Use the Consumer component for cleaner conditional rendering with multiple contexts:
import { Consumer } from "superctx";
function DataDisplay() {
return (
<Consumer providers={[UserContext, ProjectContext]}>
{([user, project]) => (
<div>
<h1>{user.name}</h1>
<h2>{project.name}</h2>
</div>
)}
</Consumer>
);
}Real-World Examples
Environment Configuration
import { createSuperContext, addBase } from "superctx";
type NebulyEnvironment = {
apiUrl: string;
environment: "development" | "production";
flags?: Record<string, boolean>;
};
export const Environment = addBase(
createSuperContext<NebulyEnvironment>({
notProvidedMessage: "Environment not provided",
}),
(Provider) => {
return ({ children }) => {
const env = getEnvironmentValues();
return <Provider value={env}>{children}</Provider>;
};
},
);
// Usage
function App() {
return (
<Environment.Base>
<Router />
</Environment.Base>
);
}
function ApiClient() {
const { apiUrl } = Environment.useProvided();
// Use apiUrl to configure API client
}Theme Context
import { createSuperContext, addBase } from "superctx";
export const Theme = addBase(
createSuperContext<"light" | "dark" | "system">({
initialValue: "system",
}),
(Provider) => {
return ({ children, defaultTheme = "system" }) => {
const [theme, setTheme] = useState(defaultTheme);
return <Provider value={theme}>{children}</Provider>;
};
},
);
// Usage
function App() {
return (
<Theme.Base defaultTheme="light">
<YourApp />
</Theme.Base>
);
}Combining Multiple Contexts
import { useProviders, Consumer } from "superctx";
// Access multiple contexts
function Dashboard() {
const [user, project, environment] = useProviders([UserContext, ProjectContext, Environment]);
return (
<div>
<h1>Welcome, {user.name}</h1>
<p>Project: {project.name}</p>
<p>Environment: {environment.environment}</p>
</div>
);
}
// Or use Consumer component for conditional rendering
function ConditionalContent() {
return (
<Consumer providers={[UserContext, FeatureFlags]}>
{([user, flags]) => {
if (flags.isAdmin && user.role === "admin") {
return <AdminPanel />;
}
return <RegularContent />;
}}
</Consumer>
);
}Formatters with Locale
import { createSuperContext, addBase } from "superctx";
import { DateFormatter, NumberFormatter, TimeFormatter } from "formatters";
export const Formatters = addBase(
createSuperContext<{
numberFmt: NumberFormatter;
timeFmt: TimeFormatter;
dateFmt: DateFormatter;
}>({
notProvidedMessage: "Formatters are not provided",
}),
(Provider) => {
return ({ children }) => {
const { language } = Localization.useProvided();
return (
<Provider
value={{
numberFmt: new NumberFormatter({ locale: language }),
timeFmt: new TimeFormatter({ locale: language }),
dateFmt: new DateFormatter({ locale: language }),
}}
>
{children}
</Provider>
);
};
},
);
// Usage
function PriceDisplay({ amount }: { amount: number }) {
const { numberFmt } = Formatters.useProvided();
return <span>{numberFmt.compactFloat(amount)}</span>;
}API Reference
createSuperContext<T>(options)
Creates a super context with enhanced features.
Options:
initialValue: T: Default value (context is always provided)getInitialValue: () => T: Lazy initialization functionnotProvidedMessage: string: Error message if provider is missing
Returns:
Provider: React context provider componentConsumer: React context consumer componentuseProvided(): Hook to access context value
addBase<T, U>(context, baseFactory)
Creates a base component for a context.
Parameters:
context: A super contextbaseFactory: Function that takes Provider and returns a base component
Returns: Context with added Base component
useProviders<T>(deps)
Hook to access multiple contexts at once.
Parameters:
deps: Array of super contexts
Returns: Tuple of context values (typed)
Consumer<T>(props)
Component for conditional rendering with multiple contexts.
Props:
providers: Array of super contextschildren: Render function receiving context values
MissingProviderError
Error class thrown when a required context is not provided.
Error Handling
When a required context is missing, superctx throws a MissingProviderError as soon as you try to read it.
This fail-fast behavior is intentional: it surfaces provider mistakes immediately, instead of silently propagating null values and causing harder-to-debug errors later.
Vanilla
import { createContext, useContext } from "react";
export const MyContext = createContext<{
foo: "bar";
}>(null!);
export function useMyContext() {
const value = useContext(MyContext);
if (!value) {
throw new Error("useMyContext requires a MyContext provider");
}
return value;
}
export function SomeComponent() {
const { foo } = useMyContext();
return <p>{foo}</p>;
}
With superctx
import { createSuperContext } from "superctx";
export const MyContext = createSuperContext<{
foo: "bar";
}>({
notProvidedMessage: "MyContext provider is missing",
});
export function SomeComponent() {
const { foo } = MyContext.useProvided();
return <p>{foo}</p>;
}