Skip to content

Instantly share code, notes, and snippets.

@jberndsen
Created October 10, 2019 09:40
Show Gist options
  • Save jberndsen/786fd955e77a804912333088d393af30 to your computer and use it in GitHub Desktop.
Save jberndsen/786fd955e77a804912333088d393af30 to your computer and use it in GitHub Desktop.
Simple DI functionality using React Context API and hooks
import React, { useContext } from 'react';
declare global {
interface Window {
__DI_CONTAINER__: Container;
}
}
type Newable<T> = new (...args: unknown[]) => T;
interface Service<T> {
identifier: string;
instance: T;
}
export interface Container {
get<T>(identifier: string): T;
bind<T>(identifier: string, service: Newable<T>): void;
bindInstance<T>(identifier: string, instance: T): void;
}
class NaiveContainer implements Container {
private services: Service<unknown>[];
public constructor() {
this.services = [];
}
public get<T>(identifier: string) {
const service = this.services.find(x => x.identifier === identifier);
if (service) {
return service.instance as T;
}
throw new Error(
`Cannot find a registered service with name: ${identifier}. Did you add it to the DI container?`
);
}
public bind<T>(identifier: string, service: Newable<T>) {
if (this.services.find(x => x.identifier === identifier)) {
throw new Error(
`Duplicate entry found in DI container for: ${identifier}`
);
}
const instance = new service(this);
this.services.push({ identifier, instance });
}
public bindInstance<T>(identifier: string, instance: T) {
if (this.services.find(x => x.identifier === identifier)) {
throw new Error(
`Duplicate entry found in DI container for: ${identifier}`
);
}
this.services.push({ identifier, instance });
}
}
const InjectorContext = React.createContext<{ container: Container | null }>({
container: null,
});
interface Props {
container: Container;
}
export const InjectorProvider: React.FC<Props> = props => {
return (
<InjectorContext.Provider value={{ container: props.container }}>
{props.children}
</InjectorContext.Provider>
);
};
export function useInjection<T>(identifier: string): T {
const { container } = useContext(InjectorContext);
if (!container) {
throw new Error(
`Unable to get DI container. Did you add the InjectorProvider to your app?`
);
}
return container.get<T>(identifier);
}
export function getContainer(): Container {
if (typeof window === 'undefined') return new NaiveContainer();
let container = window.__DI_CONTAINER__;
if (!container) {
container = new NaiveContainer();
window.__DI_CONTAINER__ = container;
}
return container;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment