Skip to content

Instantly share code, notes, and snippets.

@hediet
Last active June 3, 2019 14:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hediet/4fe250623aa6c0ebf3b93ea9c3b567d0 to your computer and use it in GitHub Desktop.
Save hediet/4fe250623aa6c0ebf3b93ea9c3b567d0 to your computer and use it in GitHub Desktop.
React Dependency Injection
import * as React from "react";
import { Container } from "inversify";
export function ref<T>(name: string): { T: T; id: string } {
return {
T: null!,
id: name
};
}
type Simplify<T> = { [TKey in keyof T]: T[TKey] };
export type Ctor<T> = new (...args: any[]) => T;
const ContainerContext = React.createContext<Container | undefined>(undefined);
export const ContainerProvider = ContainerContext.Provider;
const containerProp = "prop123";
export function withModel<TProps, TModel>(
model: new (props: TProps, ...rest: any[]) => TModel,
Component: Ctor<React.Component<{ model: TModel }>>
): React.FunctionComponent<TProps> & {
Wrapped: Ctor<React.Component<{ model: Simplify<TModel> }>>;
} {
const Inner = class extends React.Component<
TProps & { [containerProp]: Container }
> {
private model: TModel;
constructor(props: TProps & { [containerProp]: Container }) {
super(props);
const container = props[containerProp];
const innerContainer = new Container();
innerContainer.parent = container;
innerContainer.bind("props").toConstantValue(props);
innerContainer.bind(model).toSelf();
this.model = innerContainer.get(model);
}
componentWillUnmount() {
if ("dispose" in this.model) {
(this.model as any).dispose();
}
}
render() {
return <Component model={this.model} />;
}
};
const resultComponent = function(props: TProps) {
return (
<ContainerContext.Consumer>
{val => {
if (!val) {
throw new Error("Container must be provided");
}
const newProps = {
[containerProp]: val,
...props
};
return <Inner {...newProps} />;
}}
</ContainerContext.Consumer>
);
};
resultComponent.Wrapped = Component;
return resultComponent;
}
import "reflect-metadata";
import * as React from "react";
import * as ReactDom from "react-dom";
import { Container, inject, injectable } from "inversify";
import { ref, withModel, ContainerProvider } from "./helper";
// services
interface IUserService {
currentUser: string;
}
@injectable()
class UserService implements IUserService {
get currentUser(): string {
return "test";
}
}
const userServiceRef = ref<UserService>("UserService");
// gui
@injectable()
class UserComponentModel {
constructor(
@inject("props")
private readonly props: { uppercase: boolean },
@inject(userServiceRef.id)
private readonly userService: typeof userServiceRef.T
) {}
public get currentUser(): string {
const usr = this.userService.currentUser;
if (this.props.uppercase) {
return usr.toUpperCase();
}
return usr;
}
private dispose() {
console.log("On unmount");
}
}
const UserComponent = withModel(
UserComponentModel,
class extends React.Component<{ model: UserComponentModel }> {
render() {
const model = this.props.model;
return <div>Cur user: {model.currentUser}</div>;
}
}
);
class Gui extends React.Component {
private readonly container = new Container();
constructor(props: {}) {
super(props);
this.container.bind(userServiceRef.id).to(UserService);
}
render() {
return (
<ContainerProvider value={this.container}>
<UserComponent uppercase />
<UserComponent.Wrapped model={{ currentUser: "test" }} />
</ContainerProvider>
);
}
}
const main = document.createElement("div");
document.body.append(main);
ReactDom.render(<Gui />, main);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment