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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@norabytes/reactjs-ioc

v1.2.3

Published

A simple Inversion of Control library for ReactJS built on top of the injection-js library.

Downloads

367

Readme

NoraBytes © 2024

ReactJS Inversion of Control (Compatible with NextJS!)

Installation

npm install @norabytes/reactjs-ioc

or

yarn add @norabytes/reactjs-ioc

Usage

Steps to correctly enable the Reflect API.

  1. Install the @abraham/reflection library or the reflect-metadata library.
  2. Provide the installed Reflect API polyfill by either doing:
    1. In your .env file add: NODE_OPTIONS=--require @abraham/reflection or NODE_OPTIONS=--require reflect-metadata
    2. Manually import it with import '@abraham/reflection' or import 'reflect-metadata' into your app entrypoint file.
    3. Use the polyfills provider of your transpiler of choice.

Steps required only if using TS.

  1. Make sure that your TS version is at least 4.7.0
  2. In your tsconfig.json inside the compilerOptions object:
    1. "experimentalDecorators": true
    2. "emitDecoratorMetadata": true
    3. "moduleResolution": "NodeNext"

Example

Global Module

  • Create an ApiService which will be provided globally (Singleton) from the AppModule.
// ./services/api/api.service.ts

import { Injectable } from '@norabytes/reactjs-ioc';

@Injectable()
export class ApiService {
  async fetchData(endpoint: string, data: any): Promise<Response> {
    // Fetch data logic
  }
}
// ./services/api/api.module.ts

import { ProviderModule } from '@norabytes/reactjs-ioc';
import { ApiService } from './api.service';

export const ApiServiceModule = new ProviderModule({
  providers: [ApiService],
});
  • Create an UserService which will have access to the ApiService by constructor injection.
// ./services/user/user.service.ts

import { Injectable } from '@norabytes/reactjs-ioc';

@Injectable()
export class UserService {
  constructor(private readonly apiService: ApiService) {}

  async createNewUser(userData: UserData): Promise<bool> {
    // You can access the `ApiService` everywhere inside your `UserService` instance.
    const response = await this.apiService.fetchData('/api/v1/create-user', userData);
  }
}
// ./services/user/user.module.ts

import { ProviderModule } from '@norabytes/reactjs-ioc';
import { UserService } from './user.service';

export const UserServiceModule = new ProviderModule({
  providers: [UserService],
});
  • Create an app.module.ts file:
// ./app.module.ts

import { ProviderModule } from '@norabytes/reactjs-ioc';

// Modules
import { ApiServiceModule } from './api';
import { UserServiceModule } from './user';

export const AppModule = new ProviderModule({
  // By importing these modules, their `providers` will be automatically resolved by the `injector` container.
  imports: [ApiServiceModule, UserServiceModule],
});

You can now inject both the ApiService and the UserService in your ReactJS functional components by doing so:

The below is an example of an HoComponent which wraps your entire ReactJS app, this should be used to provide singletons globally down to your entire app.

// ./app.tsx

import React from 'react';
import { Injector } from '@norabytes/reactjs-ioc';
import { InjectorProvider } from '@norabytes/reactjs-ioc/r';
import { AppModule } from './app.module';
import { RootLayout } from './root.layout';

export function App({ children }: { React.ReactElement }) {
  return (
    <InjectorProvider injectInto="root" module={AppModule}>
      <RootLayout>{children}</RootLayout>
    </InjectorProvider>
  );
}
// ./pages/home.tsx

// The `Homepage` components is rendered by the `RootLayout` somewhere down the tree.

import { useEffect } from 'react';
import { useInject } from '@norabytes/reactjs-ioc/r';
import { ApiService } from '../services/api';

export function Homepage() {
  const apiService = useInject(ApiService);

  useEffect(() => {
    apiService.fetchData('url', data);
  }, []);
}
  • Limit which providers can be exported from a ProviderModule.
import { Injector, ProviderModule } from '@norabytes/reactjs-ioc';
import { UserService } from './user.service';
import { UserDataService } from './user-data.service';

const UserServiceModule = new ProviderModule({
  providers: [UserService, UserDataService],

  // By default all the listed `providers` are exported.
  // If you want to control which providers should be exported outside this `ProviderModule`,
  // you can list them here.
  //
  // In this example, when another `ProviderModule` imports the `UserServiceModule`,
  // it'll only have access to the `UserService`.
  exports: [UserService],
});

console.log(UserServiceModule.getProviders());
// => [UserService, UserDataService]

const GlobalModule = new ProviderModule({
  imports: [UserServiceModule],
});

console.log(GlobalModule.getProviders());
// => [UserService]

const userContainer = Injector.createTransientInjector({ module: UserServiceModule });

console.log(userContainer.get(UserService));
// => OK

console.log(userContainer.get(UserDataService));
// => OK

const globalContainer = Injector.createTransientInjector({ module: GlobalModule });

console.log(globalContainer.get(UserService));
// => OK

console.log(globalContainer.get(UserDataService));
// => Error: No provider for `UserDataService`.
  • Inject on-the-fly.
import { Injectable, Inject } from '@norabytes/reactjs-ioc';
import { UserDataService } from './user-data.service';

// N.B: You'll still have to provide a `ProviderModule` to an `injector`.

@Injectable()
export class UserService {
  constructor(@Inject(UserDataService) private readonly userDataService: UserDataService) {}

  private applyDataToUser(data: any): void {
    this.userDataService.applyData(data);
  }
}

NextJS

Client Components

If you are using the new app folder introduced with NextJS v13, then you should be careful to add the 'use client' directive at the top of the components which are going to use the useInject hook and the InjectorProvider HoComponent.

Server Components

FAQs:

  • How to have a root container for my entire app without using the 'use client' directive?
    • Instead of using the HoC InjectorProvider, you can use the Injector API to create a scoped container, then use that container to inject directly into your components.

      eg:

      // ./app.tsx
      
      import React from 'react';
      import { Injector } from '@norabytes/reactjs-ioc';
      import { AppModule } from './app.module';
      import { RootLayout } from './root.layout';
      
      export const MY_APP_CONTAINER_KEY = 'APP_CONTAINER_KEY';
      
      Injector.createScopedInjector({
         key: MY_APP_CONTAINER_KEY,
         module: AppModule,
         // If you have already injected some modules into the `root` container and you want to be able to also resolve
         // those dependencies when using this `scoped` container, you must also add the below line.
         fromRootInjector: true
      });
      
      export function App({ children }: { React.ReactElement }) {
        return <RootLayout>{children}</RootLayout>;
      }
      
      import { Injector } from '@norabytes/reactjs-ioc';
      import React from 'react';
      import { MY_APP_CONTAINER_KEY } from '...';
      import { MyService } from '...';
      
      export async function MyServerComponent({ children }: { React.ReactElement }) {
        // Now you have access to your service/dependency.
        //
        // N.B: While this would work, it is not the best approach, you should strive to use the `useInject`
        //      hook as it is optimized especially to be used with ReactJS functional components.
        //      Therefore the correct way would be to use the `'use client'` directive at the top of any component
        //      which must have some dependencies injected into it.
        const myService = Injector.getScoped(MY_APP_CONTAINER_KEY, MyService);
      
        return <h1>Hello NoraBytes!</h1>;
      }

Unit Tests

Below some examples of how to mock your dependencies in your Unit Tests

// ./my-component/tests/mocks/my-component.service.mock.ts

import { Injectable } from '@norabytes/reactjs-ioc';
import { MyComponentService, AnotherService } from '...';

@Injectable()
export class MyComponentServiceMock extends MyComponentService {
  constructor(private override readonly anotherService: AnotherService) {}

  override realMethod(): void {
    console.log('The `realMethod` has been mocked!');
  }
}
// ./my-component/tests/mocks/my-component.module.mock.ts

import { ProviderModule } from '@norabytes/reactjs-ioc';
import { MyComponentService, MyComponentServiceMock, AnotherService } from '...';

export const MyComponentModuleMock = new ProviderModule({
  providers: [
    { provide: MyComponentService, useClass: MyComponentServiceMock },
    AnotherService
  ];
});
// ./my-component/tests/mocks/my-component.mock.ts

import { InjectorProvider } from '@norabytes/reactjs-ioc/r';
import { MyComponent, MyComponentModuleMock } from '...';

export function MyComponentMock(props: PropsType) {
  return (
    <InjectorProvider module={MyComponentModuleMock}>
      <MyComponent />
    </InjectorProvider>
  );
}

Now you can use the MyComponentMock in your Unit Tests.

Injector API

Check also the API of the injection-js library.

As this library is natively built with TypeScript, we encourage you to just check the IInjectorFactory interface which can be found at node_modules/@norabytes/reactjs-ioc/dist/src/types/injector/injector-factory.d.ts