npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-compose-providers

v0.1.4

Published

A lightweight, type-safe helper to compose React context providers as a flat array

Readme

react-compose-providers

CI npm version unpacked size license

Compose React context providers as a flat, fully-typed array. A small helper with real type safety — extracted from a production application.

Motivation

Every React app reaches a point where the root looks like this:

<QueryClientProvider client={queryClient}>
    <ThemeProvider theme="dark">
        <I18nProvider locale="en">
            <AuthProvider>
                <RouterProvider>
                    <App />
                </RouterProvider>
            </AuthProvider>
        </I18nProvider>
    </ThemeProvider>
</QueryClientProvider>

This is the React variant of callback hell — a pyramid that grows every time a new cross-cutting concern shows up. And like callback hell, the fix is the same in spirit: flatten it.

react-compose-providers is a ~45-line helper that lets you declare those providers as a flat, fully-typed array:

import { composeProviders, provider } from 'react-compose-providers';

const AppProviders = composeProviders([
    provider(QueryClientProvider, { client: queryClient }),
    provider(ThemeProvider, { theme: 'dark' }),
    provider(I18nProvider, { locale: 'en' }),
    provider(AuthProvider),
    provider(RouterProvider),
]);

function App() {
    return (
        <AppProviders>
            <Routes />
        </AppProviders>
    );
}

This is not a panacea

The providers don't disappear — they're still there, still wrapping your app, still doing the same thing. What changes is how you read, diff, and reorder them:

  • Git history stays clean. Adding or reordering a provider is a one-line diff, not a re-indentation storm.
  • Cognitive load drops. A flat list reads top-to-bottom. A 7-level pyramid you have to unfold in your head.
  • Types stay tight. Each provider's props stay required when they should be, optional when they can be. No any[], no manual casts.

When you don't need it

If your root has two providers — just write them nested. YAGNI. This helper earns its keep when the pyramid reaches ~5 levels, not before.

The motto: make the complex simple — not by hiding complexity, but by removing the cognitive tax on reading it.

Install

npm install react-compose-providers
pnpm add react-compose-providers
yarn add react-compose-providers

Requires react >= 16.8 as a peer dependency. No other runtime dependencies.

Quick Start

import { composeProviders, provider } from 'react-compose-providers';
import { ThemeProvider } from './theme';
import { AuthProvider } from './auth';

// 1. Compose once, at module level
const AppProviders = composeProviders([
    provider(ThemeProvider, { theme: 'dark' }),
    provider(AuthProvider),
]);

// 2. Use it like any other wrapper component
function App() {
    return (
        <AppProviders>
            <Routes />
        </AppProviders>
    );
}

The first provider in the array is the outermost in the tree. provider(Component, props?) is a tiny factory that preserves each component's type so TypeScript can check props per entry.

Full Example

A realistic app shell with four providers, most of them requiring props:

// providers.ts
import { composeProviders, provider } from 'react-compose-providers';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider } from './theme';
import { I18nProvider } from './i18n';
import { AuthProvider } from './auth';

const queryClient = new QueryClient();

export const AppProviders = composeProviders([
    provider(QueryClientProvider, { client: queryClient }),
    provider(ThemeProvider, { theme: 'light' }),
    provider(I18nProvider, { locale: 'en' }),
    provider(AuthProvider),
]);
// App.tsx
import { AppProviders } from './providers';
import { Routes } from './routes';

export function App() {
    return (
        <AppProviders>
            <Routes />
        </AppProviders>
    );
}

Adding, removing, or reordering a provider is a one-line edit to providers.ts. No JSX reshuffling, no re-indentation, no silently broken nesting.

API

| Export | Description | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------- | | composeProviders(entries) | Factory that returns a React.FC<PropsWithChildren> wrapping its children in the given providers, outermost-first. | | provider(Component, props?) | Creates a typed entry for the array. The props argument is conditionally required based on the component's type. | | ProviderEntry<P> | Type of an entry. Exported for advanced use — most users can let TypeScript infer this and never reference it. |

TypeScript

The library's main feature: required props stay required. A provider with a required prop cannot be composed without passing it — at compile time, not at runtime.

import { composeProviders, provider } from 'react-compose-providers';

// Requires `theme`
function ThemeProvider({
    theme,
    children,
}: {
    theme: 'light' | 'dark';
    children: React.ReactNode;
}) {
    /* ... */
}

// No required props
function DebugProvider({ children }: { children: React.ReactNode }) {
    /* ... */
}

composeProviders([
    // ✓ Props inferred and checked
    provider(ThemeProvider, { theme: 'dark' }),

    // ✓ No props — one-arg call is allowed
    provider(DebugProvider),

    // ✗ Type error: Expected 2 arguments, but got 1
    // @ts-expect-error
    provider(ThemeProvider),

    // ✗ Type error: "red" is not assignable to 'light' | 'dark'
    // @ts-expect-error
    provider(ThemeProvider, { theme: 'red' }),
]);

This is all compile-time work. At runtime, provider() is a thin factory — no validation, no overhead, nothing to trip over.

How It Works

  1. composeProviders(entries) returns a component that uses Array.reduceRight to wrap children in each provider, outermost-first.
  2. provider() is a factory with two overloads: a one-argument form that accepts any component whose only declared prop is children, and a two-argument form that requires a matching props object. TypeScript picks the first overload when applicable, the second otherwise — so providers with required props force you to pass them at the call site.
  3. That's the whole library. ~55 lines of source, zero runtime dependencies.

Call composeProviders once, at module level (or memoize with useMemo). Calling it inside render creates a new component on every render, which remounts the entire subtree. This isn't a library quirk — it's how React treats component identity.

Comparison with Alternatives

| Library | Approach | TypeScript | Conditional required props | Maintained | | ------------------------------------------- | --------------------------- | ---------------------------- | -------------------------- | ------------ | | react-compose-providers | reduceRight helper | Full, per-provider inference | Yes | Active | | compose-providers | Similar helper | Provider<any>[] | No | Active | | react-pipeline-component | JSX <Pipeline> components | — | No | Inactive | | @ascodelife/react-context-provider-composer | Helper | Partial | No | Low activity |

Note: this pattern doesn't eliminate providers — it organizes them. If your root has 2 providers, you don't need this library.

License

MIT