I am using CRA + TS and integrated a custom hook for dependency injection(Inversify). Here is my code:
// DI Provider
export const InversifyContext = React.createContext<{ container: Container | null }>({ container: null });
export const DiProvider: React.FC<Props> = (props) => {
return <InversifyContext.Provider value={{ container: props.container }}>{props.children}</InversifyContext.Provider>;
};
// Custom Hook
export function useInjection<T>(identifier: interfaces.ServiceIdentifier<T>): T {
const { container } = useContext(InversifyContext);
if (!container) {
throw new Error();
}
console.log("This is hook"); // This gets printed infinitely
return container.get<T>(identifier);
}
// Injectable Service
@injectable()
class MyService{
// some methods
}
// index.tsx
const container = new Container();
container.bind<MyService>("myService").to(MyService);
ReactDOM.render(
<DiProvider container={container}>
<MyComponent />,
document.getElementById("root")
);
// MyComponent.tsx
const MyComponent: React.FC = () => {
const myService = useInjection<MyService>("myService");
useEffect(() => {
myService.getData(); // Loop call
}, [myService]);
}
Now when I debugged the code, I see that the provider is getting rendered infinitely, which causes the rerender of the component.
First, you need to understand why is this happening.
When you use your injected service in the useEffect
hook as a dependency (which you should), it triggers a component rerender, which will recall the useInjection
hook and a new/updated instance of MyService
is returned, because the instance is changed, the useEffect
will be triggered again and this will end you up with recursive calls.
I agree that you should not ignore the useEffect dependencies. A simple solution here would be to memoize the service within the hook.
So your memoized hook will become:
export function useInjection<T>(identifier: interfaces.ServiceIdentifier<T>): T {
const { container } = useContext(InversifyContext);
if (!container) {
throw new Error();
}
console.log("This is hook"); // This gets printed infinitely
return useMemo(() => container.get<T>(identifier), [container, identifier]);
}
Your hook will now return a memoized version of the service instance.