Dependency Injection in React
One of the challenging parts in software development is keeping code clean, maintainable and extendable. Robert C. Martin introduced five software design principles exposing the dependency management aspects of object oriented design known as SOLID. One of them is a Dependency Inversion principle that is saying “Depend on abstractions, not on concretions”. A pattern that implements this principle is called Dependency Injection (DI). This pattern makes it easier to decompose program components, write unit tests and even provision modules in run-time.
In React javascript library DI is done via Context. Let’s take a look at the Async Web Storage project in which I will apply React Context to use different mechanisms to store data.
Async Web Storage
This Web Storage will support three operations: find, upsert and remove.
A naive concrete implementation is an in-memory storage. But this idea could be extended to localStorage/sessionStorage, AWS S3, etc.
I will use React Context to provision an in-memory implementation by default. Both, in-memory and s3 storages will be exposed outside.
Let’s create a simple React component Foo that will read data from the storage. It will depend only on the abstraction injected via AsyncWebStorageContext (Fig. 4).
Switching between storages is happening with AsyncWebStorageContext.Provider (Fig. 5).
Thus, we have visual component Foo and different Async Web Storage implementations, which are independent, can be developed in parallel and there are open for extension.
Today this pattern is widely used, even built in some modern languages (e.g., Scala), but generally it’s implemented by libraries or frameworks. React library has a built-in support for DI via Context. By exposing abstractions using DI code becomes easier to follow and maintain, and the entire application — more flexible and easier to extend.