Gathering detailed insights and metrics for react-service-container
Gathering detailed insights and metrics for react-service-container
Gathering detailed insights and metrics for react-service-container
Gathering detailed insights and metrics for react-service-container
@triviality/react
Exposes triviality service container to react components
triviality-react
Exposes triviality service container to react components
thinjector
Minimalistic, super lightweight React service injection container extremely easy to use.
exstore
App container for Javascript with state management, service management use for React, Vue,...
npm install react-service-container
Typescript
Module System
Node Version
NPM Version
71.1
Supply Chain
97.5
Quality
75.2
Maintenance
100
Vulnerability
100
License
TypeScript (93.22%)
JavaScript (6.78%)
Verify real, reachable, and deliverable emails with instant MX records, SMTP checks, and disposable email detection.
Total Downloads
22,549
Last Day
4
Last Week
14
Last Month
80
Last Year
2,780
MIT License
32 Stars
58 Commits
2 Forks
3 Watchers
4 Branches
1 Contributors
Updated on Jan 03, 2025
Minified
Minified + Gzipped
Latest Version
0.2.6
Package Id
react-service-container@0.2.6
Unpacked Size
75.26 kB
Size
15.79 kB
File Count
9
NPM Version
7.8.0
Node Version
14.5.0
Cumulative downloads
Total Downloads
Last Day
300%
4
Compared to previous day
Last Week
133.3%
14
Compared to previous week
Last Month
-60.6%
80
Compared to previous month
Last Year
-6.7%
2,780
Compared to previous year
react-service-container is a library which helps provide services to your components and hooks in an easy, clean, and testable manner.
Simply define a service
1// greeter.js 2 3export default class Greeter { 4 greet() { 5 return "👋 Hello there!"; 6 } 7}
provide it within a ServiceContainer
1// App.js 2import React from "react"; 3import { ServiceContainer } from "react-service-container"; 4import Greeter from "./greeter"; 5import Greeting from "./Greeting"; 6 7export default function App() { 8 return ( 9 <ServiceContainer providers={[Greeter]}> 10 <Greeting /> 11 </ServiceContainer> 12 ); 13}
and use it within your components:
1// Greeting.js 2import React from "react"; 3import { useService } from "react-service-container"; 4import Greeter from "./greeter"; 5 6export default function Greeting() { 7 const greeter = useService(Greeter); 8 return <h1>{greeter.greet()}</h1>; 9}
Testing your components is a breeze:
1import React from "react"; 2import { render } from "@testing-library/react"; 3import "@testing-library/jest-dom/extend-expect"; 4import Greeter from "./greeter"; 5import Greeting from "./greeting"; 6 7// Greeting.spec.js 8test("renders a greeting", () => { 9 const fakeGreet = jest.fn(); 10 fakeGreet.mockReturnValue("expected greeting"); 11 12 const { asFragment } = render( 13 <ServiceContainer 14 providers={[{ provide: Greeter, useValue: { greet: fakeGreet } }]} 15 > 16 <Greeting /> 17 </ServiceContainer> 18 ); 19 20 expect(asFragment()).toHaveTextContent("expected greeting"); 21});
View a working example of the above code
The library is based off of the Service Locator pattern, and the API is inspired by apollo-client.
Unlike similar libraries, this is not a dependency injection library. The sole purpose of this library is to provide components with services.
When developing React applications, I find that using Context together with Custom Hooks provides a really clean and powerful way to inject services into components. For example, if I have a Greeter
service, such as I used in the example above,
I could write something like this:
1// greeter.js 2import { createContext, useContext } from "react"; 3 4export default class Greeter { 5 greet() { 6 // ... 7 } 8} 9 10export const GreeterContext = createContext(null); 11 12export function useGreeter() { 13 const instance = useContext(Greeter); 14 if (!instance) { 15 throw new Error(`[useGreeter] Greeter was never provided`); 16 } 17 return instance; 18}
Then, in my application code I can provide Greeter at runtime, again as shown above:
1// App.js 2import React from "react"; 3import Greeter, { GreeterContext } from "./greeter"; 4import Greeting from "./Greeting"; 5 6const greeter = new Greeter(); 7export default function App() { 8 return ( 9 <GreeterContext.Provider value={greeter}> 10 <Greeting /> 11 </GreeterContext.Provider> 12 ); 13}
NOTE: In a real app,
Greeting
may be nested somewhere deep down in the component tree, while in this example it may look like it would just be easier to pass it as a prop, in a real application you'd have to pass it down an entire component tree, making this method more appealing (to me at least).
Finally, I could use my custom hook in my Greeting
component:
1// Greeting.js 2import React from "react"; 3import { useGreeter } from "./greeter"; 4 5export default function Greeter() { 6 const greeter = useGreeter(); 7 return <h1>{greeter.greet()}</h1>; 8}
This not only makes it super easy for components to consume services, but once I started using it, I realized I also preferred providing service mocks in tests explicitly vs. using Jest's module mocking. I found that explicitly specifying the services my components rely upon made me less likely to mock out implementation details and ensure I drew clear boundaries around separation of concern.
I also prefer to encapsulate services as classes (call me old-school I guess?), and found Jest's ES6 class mocking to be a bit difficult. This of course is just my personal opinion :sweat_smile:
However, once I started doing this with multiple services, e.g. FooService
, BarService
, BazService
, I started to get into this slippery slope where not only was I writing a ton of boilerplate code for every service, but my code started looking more diagonal vs. vertical when declaring services.
1import FooService, { FooContext } from "./fooService"; 2import BarService, { BarContext } from "./barService"; 3import BazService, { BazContext } from "./bazService"; 4 5const foo = new Foo(); 6const bar = new Bar(); 7const baz = new Baz(); 8export default function App() { 9 <FooContext.Provider value={foo}> 10 <BarContext.Provider value={bar}> 11 <BazContext.Provider value={baz}>{/* ... */}</BazContext.Provider> 12 </BarContext.Provider> 13 </FooContext.Provider>; 14}
I wanted a way to generalize the concept of providing services via contexts and hooks in an easy and intuitive manner, and took inspiration from Angular's dependency injection system to do so (but without the complexity that true DI comes with). This turned out to work well for my use cases, and hence react-service-container
was born.
With react-service-container
, the above becomes:
1import FooService from "./fooService"; 2import BarService from "./barService"; 3import BazService from "./bazService"; 4import { ServiceContainer } from "react-service-container"; 5 6export default function App() { 7 <ServiceContainer providers={[FooService, BarService, BazService]}> 8 {/* ^_^ */} 9 </ServiceContainer>; 10}
Not to mention no more Context
/ hook definition boilerplate in your services.
npm i -S react-service-container
UMD builds can be found in the npm package's umd/
folder, containing both development (react-service-container.js
)
and production (react-service-container.min.js
) builds. Source maps are included in the folder.
If you'd like to include react-service-container using a <script>
tag, you can use unpkg to
do so.
1<script src="https://unpkg.com/react-service-container/umd/react-service-container.min.js"></script>
In order to use react-service-container
, you must create a top-level ServiceContainer
component, and pass it
a list of providers via its providers
prop that tell react-service-container
what services are available,
and how to constructor them.
1import Greeter from "./greeter"; 2import ApiClient from "./apiClient"; 3 4ReactDOM.render( 5 <ServiceContainer 6 providers={[ 7 Greeter, 8 { 9 provide: ApiClient, 10 useValue: new ApiClient({ endpoint: "http://example.com/api" }), 11 }, 12 ]} 13 > 14 <PageComponent /> 15 </ServiceContainer> 16);
You can then use the useService()
hook within your components in order to make use of a service.
NOTE: Each service is only instantiated once, the first time useService()
is called.
If you're familiar with Angular's DI providers, you're familiar with ours. The API is pretty much the same.
Providers come in two forms:
provide
key whose value is the service token you wish to use to represent the service provided, and an additional key the options of which are described below.Function
object you pass as a shorthand for {provide: Function, useClass: Function}
The providers you can use are listed below in the code example:
1class MyService {} 2 3const providers = [ 4 MyService, // Class shorthand 5 { provide: MyService, useClass: MyService }, // Equivalent to the above 6 { provide: MyService, useValue: new MyService() }, // Provide a concrete value to be used 7 { provide: MyService, useFactory: () => new MyService() }, // Provide a factory function, useful for configuring the service 8];
You can also alias dependencies via useExisting
1class NewService {} 2 3const providers = [MyService, { provide: NewService, useExisting: MyService }];
This is useful for gradually deprecating APIs.
See the tests in this repo for example of using each.
react-service-container
fully supports hierarchal <ServiceContainer>
components. When <ServiceContainer>
components are nested within one another, useService()
calls act exactly like variable lookups: starting from the inner-most to the outer-most service container, the first service token found will be used.
Using hierarchal service containers not only lets you keep different parts of your codebase cleanly separated, but it allows for lazy loading of services at the time in which you need it.
Say you have an app which shows a list of TODOs, as well as a settings page. You lazily load the page since users tend to navigate to either one page or the other, most likely TODOs. Chances are that the services for the TODOs page might be different than those used for the settings page. However, some, such as getting information about the current user, might be shared across the entire application. Using hierarchal service containers allows the top-level application to contain shared modules, while lazily loaded feature modules can configure their services at load time.
1// src/todos/index.js 2 3import React from "react"; 4import { ServiceContainer } from "react-service-container"; 5import TodosService from "./todosService"; 6 7export default function Todos() { 8 return ( 9 <ServiceContainer providers={[TodosService]}> 10 {/* Render TODOs */} 11 </ServiceContainer> 12 ); 13}
1// src/settings/index.js 2 3import React from "react"; 4import { ServiceContainer } from "react-service-container"; 5import SettingsService from "./settingsService"; 6 7export default function Settings() { 8 return ( 9 <ServiceContainer providers={[SettingsService]}> 10 {/* Render settings */} 11 </ServiceContainer> 12 ); 13}
1// src/App.js 2 3import React, { Suspense, lazy } from "react"; 4import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 5import { ServiceContainer } from "react-service-container"; 6 7// Common dependency 8import UserService from "./userService"; 9// Loading indicator 10import Loading from "./components/Loading"; 11 12// Lazily loaded components 13const Todos = lazy(() => import("./todos")); 14const Settings = lazy(() => import("./settings")); 15 16export default function App() { 17 return ( 18 <ServiceContainer providers={[UserService]}> 19 <Router> 20 <Suspense fallback={<Loading />}> 21 <Switch> 22 <Route path="/app" component={Todos} /> 23 <Route path="/settings" component={Settings} /> 24 <Route exact path="/"> 25 <Redirect to="/app" /> 26 </Route> 27 </Switch> 28 </Suspense> 29 </Router> 30 </ServiceContainer> 31 ); 32}
Now any components/hooks under Todos
or Settings
can call useService(UserService)
, but only components/hooks under Todos
can
call useService(TodosService)
and same for Settings
and useService(SettingsService)
;
Provide is simple. provide
can be any JS object. Theoretically {provide: 'hello!', useValue: {...}}
would work.
If you want to inject something that's not a class, try using Symbols.
1/** config.js */ 2 3export const config = { 4 ..., 5}; 6 7// NOTE: A string would work fine as well, if you wanted to be simpler. My preference 8// is to go for Symbols since they're completely unambiguous. 9export const CONFIG = Symbol.for("config"); 10 11/** App.js */ 12 13import {CONFIG, config} from "./config"; 14 15function App() { 16 return <ServiceContainer providers={[{provide: CONFIG, useValue: config}]}>{...}</ServiceContainer> 17}
Service containers can be easily used without hooks in class components as well. Simply set
the component's contextType
property to ServiceContainerContext
and use this.context.get()
inside the render method or anywhere else that's needed.
1/* Greeting.js */ 2 3import React from "react"; 4import { ServiceContainerContext } from "react-service-container"; 5import Greeter from "./greeter"; 6 7class MyComponent extends React.Component { 8 static contextType = ServiceContainerContext; 9 10 render() { 11 const greeter = this.context.get(Greeter); 12 return <p>{greeter.greet()}</p>; 13 } 14}
react-service-container
is written in TypeScript, and comes with first-class support for it. When using Function
objects, such as class constructors, with useService()
, it is properly typed as an instance of that constructor.
Let's take the example from the introduction and rewrite it in TypeScript.
1// greeter.ts 2 3export default class Greeter { 4 greet(): string { 5 return "👋 Hello there!"; 6 } 7}
1// App.tsx 2import React from "react"; 3import { ServiceContainer } from "react-service-container"; 4import Greeter from "./greeter"; 5import Greeting from "./Greeting"; 6 7export default function App() { 8 return ( 9 <ServiceContainer providers={[Greeter]}> 10 <Greeting /> 11 </ServiceContainer> 12 ); 13}
1// Greeting.tsx 2import React from "react"; 3import { useService } from "react-service-container"; 4import Greeter from "./greeter"; 5 6export default function Greeting() { 7 const greeter = useService(Greeter); 8 return <h1>{greeter.greet()}</h1>; 9}
In the above component, greeter
is correctly typed to Greeter
, ensuring type correctness and consistency.
What about the config
example earlier?
1/** config.ts */ 2 3export interface Config {/* ... */} 4 5export const config: Config = { 6 ..., 7}; 8 9export const CONFIG = Symbol.for("config"); 10 11/** App.tsx */ 12 13import {CONFIG, config} from "./config"; 14 15function App() { 16 return <ServiceContainer providers={[{provide: CONFIG, useValue: config}]}>{...}</ServiceContainer> 17}
Here's how we might use that in a component:
1// Component.tsx 2import { useService } from "react-service-container"; 3import { CONFIG } from "./config"; 4 5export default function Component() { 6 const config = useService(CONFIG); 7 // render component 8}
Here, config
is typed as any
. This is because based on the given Symbol, TypeScript does not statically know
what type the value associated with that symbol is; the symbol could represent any type.
However, because it's cast to any
, we can easily typecast the result
1import { useService } from "react-service-container"; 2import { CONFIG, Config } from "./config"; 3 4export default function Component() { 5 const config = useService(CONFIG); 6 // render component 7} 8const config = useService(CONFIG) as Config;
This is still less than ideal, since the TypeCasting is ugly and can be repetitive. Here is my preferred approach:
1// config.ts 2import {useService} from 'react-service-container'; 3 4export interface Config {/* ... */} 5 6export const config: Config = { 7 ..., 8}; 9 10const configToken = Symbol.for("config"); 11 12export const CONFIG_PROVIDER = { 13 provide: configToken, 14 useValue: config 15}; 16 17export const useConfig = () => useService(configToken) as Config;
By providing a custom useConfig
hook, and defining the provider within the component, it dramatically reduces the
error surface and repetition of doing manual type-checking, and allows you to abstract away the service token for the config itself.
1// App.tsx 2import {ServiceContainer} from 'react-service-container'; 3import {CONFIG_PROVIDER} from './config'; 4 5function App() { 6 return <ServiceContainer providers={[CONFIG_PROVIDER]}>{...}</ServiceContainer> 7} 8 9// Component.tsx 10 11import {useConfig} from './config'; 12 13export default function Component() { 14 const config = useConfig(); 15 // render component using config, which is now correctly typed. 16}
MIT
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
Found 0/13 approved changesets -- score normalized to 0
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
16 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-03-03
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More