inversify-hooks
v4.0.0
Published
Dependency injection for React with TypeScript hooks, powered by InversifyJS.
Maintainers
Readme
Inversify Hooks
Dependency injection for React + TypeScript, the easy way. inversify-hooks lets your components resolve services from an InversifyJS container through a single useInject hook — no providers, no boilerplate. It's a thin React layer over inversify-props, which is built on inversify 8.

Table of contents
- Why
- Installation
- TypeScript configuration
- Quick start
- Registering dependencies
- Injecting into components
- Injecting into other services
- Testing with mocks
- API reference
- Runnable example
- Use it as an agent skill
- Troubleshooting
- Credits
Why
Sharing services across a React tree usually means prop-drilling or hand-rolled context. With inversify-hooks you register a class once and pull a fully-wired instance into any component:
const [userService] = useInject<IUserService>(cid.IUserService);That's the whole API surface for a component. The container, scoping, and wiring are handled for you by InversifyJS underneath.
Installation
npm install inversify-hooksinversify-props (and transitively inversify) is a regular dependency, pulled in for you; react (>= 16.8, for hooks) is the only peer dependency. TypeScript type definitions ship with the package, and both ESM and CommonJS builds are included. No separate reflect-metadata install is needed — inversify 8 no longer requires it.
Note: inversify 8 is ESM-first. Bundler users (Vite, Next, webpack, etc.) need nothing special. Consuming it from a plain CommonJS Node app via
require()needs Node 20.19+ or 22+.
TypeScript configuration
Decorator support is required, or injection silently does nothing:
{
"compilerOptions": {
"target": "es2020",
"lib": ["es2020", "dom"],
"moduleResolution": "bundler",
"experimentalDecorators": true,
"useDefineForClassFields": false
}
}⚠️ Keep
useDefineForClassFieldsfalse (the default whentargetis belowES2022). With ES class-field define semantics enabled, an instance field shadows the injected getter and the property comes backundefined. See Troubleshooting.
Quick start
// main.tsx — app entry point
import { createRoot } from 'react-dom/client';
import { container } from 'inversify-hooks';
import App from './App';
import { IUserService, UserService } from './services';
container.addSingleton<IUserService>(UserService);
createRoot(document.getElementById('root')!).render(<App />);// App.tsx
import { cid, useInject } from 'inversify-hooks';
import { IUserService } from './services';
export default function App() {
const [userService] = useInject<IUserService>(cid.IUserService);
return <h1>Hello, {userService.getName()}</h1>;
}Registering dependencies
Register on the shared container before the app renders. Three lifetimes are available:
import { container } from 'inversify-hooks';
container.addSingleton<IUserService>(UserService); // one shared instance (most common)
container.addTransient<ILogger>(Logger); // a new instance on every resolve
container.addRequest<IUnitOfWork>(UnitOfWork); // one instance per request scopeThe generic (<IUserService>) is just the compile-time type. The runtime id is derived from the class name and cached under both UserService and IUserService, which is why cid.IUserService works. Need an explicit id? Pass one:
container.addSingleton<IUserService>(UserService, 'MyUserService');const [userService] = useInject<IUserService>('MyUserService');Injecting into components
useInject<T>(id) returns a one-element tuple (so you can name the variable on destructure):
function Profile() {
const [userService] = useInject<IUserService>(cid.IUserService);
useEffect(() => {
userService.load();
}, [userService]);
return <span>{userService.getName()}</span>;
}Injecting into other services
Services can depend on other services via @inject() property injection. The id is resolved from the property name, so name the property after the class you registered:
import { inject, injectable } from 'inversify-hooks';
@injectable()
export class OrderService implements IOrderService {
@inject() private userService!: IUserService; // resolves the "UserService" registration
placeOrder() {
return this.userService.getName();
}
}Mark every injectable class with @injectable(). Property injection is lazy: the dependency is pulled from the container the first time it's accessed.
Testing with mocks
Swap a real implementation for a fake and reset between tests:
import { cid, mockSingleton, resetContainer } from 'inversify-hooks';
afterEach(() => resetContainer());
it('greets the user', () => {
mockSingleton<IUserService>(cid.IUserService, FakeUserService);
// render the component / call the service and assert
});mockTransient and mockRequest have the same signature. resetContainer() unbinds everything.
API reference
| Export | Description |
| --- | --- |
| useInject<T>(id) | React hook. Returns [T] — the instance resolved for id. |
| container | The shared container instance (auto-created). |
| container.addSingleton<T>(Class, id?) | Register a single shared instance. |
| container.addTransient<T>(Class, id?) | Register a new instance per resolve. |
| container.addRequest<T>(Class, id?) | Register one instance per request scope. |
| cid | Cache of generated ids, e.g. cid.IUserService. |
| inject / Inject | Property/parameter decorator for injecting into services. |
| injectable | Class decorator marking a class as injectable. |
| mockSingleton / mockTransient / mockRequest | Replace a registered id with another implementation (testing). |
| resetContainer() | Unbind everything from the container. |
| getContainer() / setContainer(opts) | Access or replace the underlying InversifyJS container. |
| Container | The container class, if you want your own instance. |
Runnable example
A complete React + Vite example lives in examples/react-example:
# from the repo root — build the library first
npm install && npm run build
cd examples/react-example
npm install
npm run devUse it as an agent skill
This repo ships an Agent Skill so AI coding agents (Claude Code, Cursor, etc.) know how to wire up DI with this library. Install it with npx skills:
npx skills add CKGrafico/inversify-hooksTroubleshooting
| Symptom | Cause & fix |
| --- | --- |
| Injected property is undefined | useDefineForClassFields is true. Set it to false (or keep target below ES2022). |
| Works in dev, breaks in production | The minifier mangled class names, so cid.IXxx is undefined. Enable keepNames (esbuild/Vite) or the equivalent Terser/Uglify setting. |
| No matching bindings found | The service wasn't registered, or registration ran after the component resolved. Register before render. |
| Decorators throw at runtime | experimentalDecorators is off in your tsconfig.json. |
Credits
A thin React layer over inversify-props, built on InversifyJS. Licensed under MIT.
