In this post I want to explore the idea of using contexts in React to implement dependency injection (DI) in our application.
We’ll cover two things:
- What contexts are in React and how they work
- Why we care about DI and how we can implement them using contexts.
React and Contexts
Contexts were formalized in React 0.12, and are planned for React 1.0. They
are a mechanism for a component to pass properties down to their descendent components.
The difference between props
and context
is that context
chains to descendents,
whereas props
do not.
Let’s take a look at a small example.
In our Parent
component, we implement two things:
- The
childContextTypes
static property, which specifies the propeties that this component will provide to its descendent components. - The
getChildContext()
method, which returns the concrete values for the context.
Our GrandChild
component implements the contextTypes
static property, which
specifies what will be on its context (accessible via this.context
).
This is powerful because we decoupled GrandChild
’s dependency on a user service to our
containing component (Parent
in this case).
Child
component does not need to chain the context
down to GrandChild
. This is different from props
where we would need
to manually chain all properties down.
Dependency Injection
Dependency injection is a pattern that implements the Inversion of Control design.
In recent years it has gained more mindshare with frontend developers due to the popularity of the AngularJS framework.
The value of DI is that we can depend on abstract ideas instead of concrete implementations. This makes our code much more testable and increases reusability – we’ll see this later.
Example: Random Number Generator
Let’s imagine a component RandomNumber
that renders a randomly generated integer between 1 and max
.
The current implementation couples the component to two functions: Math.round
and Math.random
. This
coupling makes testing impossible, and also makes it impossible to reuse if our random number provider changes.
Using Contexts
Let’s see how the same component looks using contexts.
This makes testing very easy because we now control exactly what random
and round
return
to our component.
We have also increased reusability of our component by allowing late-binding of the random
and round
functions. If we wanted to provide different rounding strategies we will be able to – say rounding
to two decimal places. We can also swap out Math.random
with a better number generator if needed and
we will not need to touch RandomNumber
’s implementation.
Generalization
We can extend our random number generator example to all services. In the context of Flux, we can use DI for Stores and ActionCreators so that our components are completely decoupled from concrete types.
Taking It Up A Notch
With ES7 decorators, we can create even nicer abstractions for DI in React.
Let’s take a look at two decarators:
@inject
- sets thecontextTypes
on our component.@provide
- defineschildContextTypes
on our component and binds the context.
If we go back to our earlier AppContainer
and UserAvatar
code, here’s how we can use the new decorators
in our application.
This is using a concept called higher-order components (HoC). Basically, we use functions (decorators) that take in a component as input and ouputs a component. This allows us to add additional behaviour or metadata to the original component.
Wrap-Up
In this post we saw how dependency injection can be achieved in React through the use of contexts.
We saw how dependency injection can make our code more testable and more reusable.
Further Readings
- Introduction to Contexts in React by Dave King