Skip to content

Instantly share code, notes, and snippets.

@ChristiaanScheermeijer
Created May 23, 2023 19:50
Show Gist options
  • Save ChristiaanScheermeijer/f09a8c7fc1e5789e489d1cfa774efd8e to your computer and use it in GitHub Desktop.
Save ChristiaanScheermeijer/f09a8c7fc1e5789e489d1cfa774efd8e to your computer and use it in GitHub Desktop.
Simple TypeScript DI with React
import React from 'react';
import { ServiceContainer } from './ServiceContainer';
import ContainerProvider from './ContainerProvider';
import { Test } from './Test';
import App from './App';
const container = new ServiceContainer();
// register all service classes in the container
container.register(Test);
export default () => (
<ContainerProvider container={container}>
<App />
</ContainerProvider>
);
import React, { useContext } from 'react';
import type { ServiceContainer, ServiceIdentifier } from './ServiceContainer.ts';
const ContainerContext = React.createContext<ServiceContainer | null>(null);
const ContainerProvider = ({ children, container }: { children: JSX.Element; container: ServiceContainer }) => {
return <ContainerContext.Provider value={container}>{children}</ContainerContext.Provider>;
};
export function useService<T = unknown>(service: ServiceIdentifier<T>): T {
const container = useContext(ContainerContext);
if (!container) throw new Error('Container not initialized');
return container.get(service);
}
export default ContainerProvider;
export type Constructable<T> = new (...args: any[]) => T;
export type ServiceIdentifier<T = unknown> = Constructable<T>;
export type ServiceMetadata = {
id: ServiceIdentifier<unknown>;
type: Constructable<unknown>;
value: unknown;
};
const EMPTY_VALUE = Symbol('EMPTY_VALUE');
export class ServiceContainer {
services: ServiceMetadata[] = [];
register<T extends ServiceIdentifier<unknown>>(service: T) {
const metadata: ServiceMetadata = {
id: service,
type: service,
value: EMPTY_VALUE,
};
this.services.push(metadata);
}
has<T extends ServiceIdentifier<unknown>>(service: T) {
return this.services.some((metadata) => metadata.type === service);
}
get<T = unknown>(service: ServiceIdentifier<T>): T {
const metadata = this.services.find(metadata => metadata.type === service);
if (!metadata) throw new Error(`Service not found ${service.name.toString()}`);
// instantiate
if (metadata.value === EMPTY_VALUE) {
metadata.value = new metadata.type();
}
return metadata.value as T;
}
}
import React, { useEffect } from 'react';
import { Test } from './Test';
import { useService } from './ContainerProvider';
const SomeContainer = () => {
const test = useService(Test);
useEffect(() => {
test.method1();
}, [test]);
return <h1>Hello World!</h1>;
}
export default SomeContainer;
export class Test {
constructor() {
console.log('constructing Test class!');
}
method1() {
console.log('Called method 1!!');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment