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-context-di

v1.0.1

Published

A lightweight React Context-based dependency injection container

Readme

react-context-di

A lightweight, type-safe dependency injection container for React, built on top of Context. No decorators, no reflection, no runtime magic - just a factory function, a Context, and a couple of hooks/HOCs for wiring dependencies into components.

Why

React Context is great for passing values down a tree, but using it directly means every component that needs a dependency has to know about the whole context shape, and there's no easy way to swap implementations in tests. react-context-di wraps Context in a small, typed API so you can:

  • Declare a single container type describing everything your app can inject (services, repositories, use-cases, whatever you have).
  • Pull out only the dependencies a given hook or component actually needs, fully typed, with no casting.
  • Swap the container wholesale in tests - no per-dependency mocking.

Install

npm install react-context-di

react (>=18) is a peer dependency - install it if you haven't already.

Quick start

1. Describe your container

// di/di.types.ts
import { UserService } from "../services/user.service";

export interface DiContainer {
  userService: UserService;
}

2. Create the DI context

// di/di.ts
import { createDependencyInjectionContext } from "react-context-di";
import type { DiContainer } from "./di.types";

const { DependenciesContext, inject, withInjections } =
  createDependencyInjectionContext<DiContainer>();

export const DI = {
  Provider: DependenciesContext.Provider,
  withInjections,
  inject,
};

3. Create context value

// di/di.registry.ts
import { UserService } from "../services/user.service";
import { type DiContainer } from "./di.types";

export function registerDiContainer(): DiContainer {
  return {
    userService: new UserService(),
  };
}

4. Provide the container at the root of your app

import { DI } from "./di/di";
import { registerDiContainer } from "./di/di.registry";
import { ViewHome, ViewSimpleHome } from "./views/home.view";

const diContainer = registerDiContainer();

export function App() {
  return (
    <DI.Provider value={diContainer}>
      <ViewHome name="Tony" />
      <ViewSimpleHome />
    </DI.Provider>
  );
}

5. Inject dependencies into a hook

inject lets you build a hook that receives dependencies from the container, plus any of its own arguments, fully typed.

import type { UserService } from "../services/user.service";
import { DI } from "../di/di";

interface UseGreetInjections {
  userService: UserService;
}

interface UseGreetProps {
  name: string;
}

// In generic type we specify what we want to inject + UseGreetProps
// in order to inherit them for the return function.
// Inside inject arguments we specify what exactly we want to inject.
export const useGreet = DI.inject<"userService", UseGreetProps>("userService")(
  ({ userService, name }: UseGreetInjections & UseGreetProps) => {
    return {
      sayHello: () => userService.greet(name),
    };
  },
);

// You can skip generic type for inject function if your hook
// doesn't have any arguments.
export const useSimpleGreet = DI.inject("userService")(({ userService }) => {
  return {
    sayHello: () => userService.greet("John Doe"),
  };
});

6. Inject dependencies straight into a component

import { DI } from "../di/di";
import { useSimpleGreet, useGreet } from "./use-greet";

type ViewHomeProps = {
  name: string;
};

// In generic type we specify what we want to inject + ViewHomeProps
// in order to inherit them for the return component.
// Inside withInjections arguments we specify what exactly we want to inject.
export const ViewHome = DI.withInjections<"userService", ViewHomeProps>(
  "userService",
)(({ userService, name }) => {
  const greet = userService.greet(name);
  const { sayHello } = useGreet({ name });

  return (
    <div>
      <p>Greet: {greet}</p>
      <button onClick={sayHello}>say hello</button>
    </div>
  );
});

// You can skip generic type for withInjections function if your
// component doesn't have any arguments.
export const ViewSimpleHome = DI.withInjections("userService")(({
  userService,
}) => {
  const greet = userService.greet("John Doe");
  const { sayHello } = useSimpleGreet();

  return (
    <div>
      <p>Greet: {greet}</p>
      <button onClick={sayHello}>say hello</button>
    </div>
  );
});

Note that userService is not passed by the caller - it's injected automatically from the container, so the public prop type only exposes name.

Testing

Since the container is provided via a single Provider, tests can swap in fakes without mocking individual modules:

test("renders greet", async () => {
  const fakeContainer = {
    userService: {
      greet: vi.fn(),
    },
  };

  render(
    <DependenciesContext.Provider value={fakeContainer}>
      <ViewHome name="Tony" />
    </DependenciesContext.Provider>,
  );

  expect(await screen.findByText("Hello, Tony")).toBeInTheDocument();
});

API

createDependencyInjectionContext<D>()

Creates a new, isolated DI context typed to your container shape D. Returns:

| Export | Description | | ------------------------- | ----------------------------------------------------------------------------------------------- | | DependenciesContext | A React Context. Use DependenciesContext.Provider to supply the container at your app's root. | | useDeps(keys) | Hook. Returns an object containing only the requested keys from the container. | | inject(...keys) | Higher-order function for hooks: injects dependencies plus any extra arguments into a hook. | | withInjections(...keys) | HOC: injects dependencies into a component as props, alongside its own props. |

useDeps throws if called outside a DependenciesContext.Provider - always wrap your app (or your tests) in the provider before rendering anything that injects dependencies.

Design notes

  • No runtime overhead beyond Context. There's no service registry lookup, no reflection, no metadata - useDeps is a useContext call plus a reduce over the keys you asked for.
  • One container, one source of truth. All your app's injectable dependencies live in a single typed object. Swapping implementations (e.g. for tests, or for a mock backend) means swapping one object, not hunting down individual imports.
  • Stable container values. Since the context value is stable and nothing mutates it, application doesn't have unnecessary re-renders.

License

MIT