Tim Kerr•October 1, 2024
Inversion of control and observability for React codebases
Along with posting blogs to announce updates to the Labelbox platform, we also love to capture learnings and best practices from our engineering teams. Today we are sharing important lessons learned from building complex frontends with Inversion of Control (IoC).
Read on to learn about how IoC can help React developers build adaptable, testable, and scalable applications by decoupling components and streamlining dependencies.
Introduction to Inversion of Control (IoC)
Inversion of Control (IoC) is a software engineering principle where the control of a program’s flow is transferred from the traditional flow of execution to a framework or external source. Instead of the program making direct calls to components or services when needed, the control is inverted, allowing an external entity (like a framework or container) to manage the instantiation and lifecycle of those components.
In React, you can use IoC effectively with libraries like InversifyJS to manage dependency injection, and MobX to create observable services and view models that automatically re-render React components when the data changes. These two libraries together form a synergy that allow you to implement powerful abstractions with practically no boilerplate.
In this post, we’ll walk through what qualities we like to see in a React codebase and how IoC can help us get there. We’ll also walk through setting up IoC in a React application using InversifyJS and MobX.
What do we want to achieve in our front end codebases?
- Low coupling: Inversion of Control (IoC) leads to low coupling in code by decoupling the implementation of a class from its dependencies, allowing flexibility in how those dependencies are provided. Services and view models implement the dependency injection pattern that provides dependencies of the module in the constructor of the class. These dependencies are referenced by their abstraction (interface) as opposed to their concrete implementation. This unlocks the ability to easily swap out a concrete implementation of a dependency based on the needs of our current application.
- Component portability: Traditionally, React components tightly manage their dependencies, often importing and instantiating services directly in the component body via hooks. This approach tightly couples components to specific implementations, reducing reusability. With IoC, components no longer manage their own dependencies. Instead, dependencies like services are injected at runtime through an IoC container. This decoupling allows the component to be used in different contexts by simply injecting different implementations of the services.
- Testability: Given that React components typically manage their dependencies directly, this makes it difficult to swap these dependencies for mocks or stubs during testing, leading to complex testing setups. With IoC, all classes take in their dependencies through the constructor, which makes providing mock services and data very straightforward.
Using IoC in React
Setting up the IoC Container Provider
The IoC container is the mechanism that will direct the control flow of our application. We define a React provider that exposes the InversifyJS IoC container to the rest of our application like this:
Creating a View Model that consumes a service managed by the IoC Container
In consumer classes, dependencies are taken in as parameters in the constructor and referenced by their abstraction (interface). This allows the underlying implementation of the dependency to be swapped out if need be in a different context.
Observability with MobX in React
MobX automatically triggers React component re-renders by making properties on a referenced model observable. When a component references an observable property on a MobX model, MobX tracks this dependency. If the property changes, MobX efficiently updates only the components that depend on it, ensuring automatic re-renders without manual intervention. This reactive behavior allows for more responsive UI updates based on state changes with less boilerplate code.
Referencing the view model in a React component
Conclusion
Inversion of Control (IoC) is a key principle for creating flexible, testable, and maintainable React codebases. By decoupling components from their dependencies and using tools like InversifyJS and MobX, we can streamline service management and state updates. This approach not only reduces the boilerplate but also makes components more portable and easier to test. Embracing IoC ensures that your React application stays clean, adaptable, and scalable as it grows.