Inversion of Control and DI in Reactjs and Redux

In this article, we will explore how to apply inversion of control to react and redux.

The article builds on the concepts of IOC/DI, so you might want to read my previous two articles on those:

The issue

Using react and redux as described in various tutorials, there was always something that didn’t feel quite right. The reason has to do with the import statement. Actually not with the import statement itself, but where it is used. For some reason, when it comes to building react components, it is very common to rely only on direct module imports. Why is this a problem? Well the problem is the same as for any code that uses singletons instead of dependency injection (see the two linked articles). React is no different. But just in case, here are a few concrete problems:

Problem 1: Passing props through intermediary components

The concept of props is itself a form of dependency injection, but DI through props alone isn’t enough. A common case is that of passing a prop through multiple components, even though the intermediary components don’t use the prop themselves. This problem can be solved in multiple ways:

One way is to just pass along any extra props with the spread operator:

<IntermediaryComponent {...props} />

This approach is pretty nasty since you have to litter basically the whole codebase with the above style. Also, you often end up passing along more props than is needed.

Another approach is to use the context API (Redux uses this approach by default, but can also do it in other ways). However, this approach is a clunky and unecessary hack compared to the more universal approach of inversion of control. More on that later.

Problem 2: Hard dependencies on redux and others

Oftentimes, components are connected to redux and exported directly. Redux admits this is a problem since it advocates differentiating between “container” components and “presentational/dumb” components (for this reason and others). The container components have a direct dependency on redux, so to mitigate the issues with this you can make a “dumb” reusable component next to it. There is still the issue that other components rely directly on the connected container component though. The issue is that components know about redux in the first place. This is totally unecessary, and intertwines the concern of configuration with the concern of building components.

What we actually want is to just make react components without anymore fuss. No decorators, no connect’s, no observables, no nothing. We want to move all this stuff somewhere else.

Dependency injection in react

So now that we’ve seen a couple of problems with using direct module imports, let’s see how it can be done without them.

At first glance, this seems a bit difficult to do with react since we’re not in control of the component constructors ourselves. However, since classes in javascript are really just functions, there’s nothing stopping us from wrapping those functions in other functions, and use those as our base for dependency injection. This pattern is usually called higher order functions, but in react it is often referred to as higher order components.

So in essence, dependency injection in react looks like this:

export default World =>
class Hello extends Component{
render(){
return (
<div>
Hello <World />
</div>
)
}
}

// Or with stateless functional components
export default World => props => <div>Hello <World /></div>

So what have we gained by doing this? First of all, we have let go of the control of our dependencies (i.e. inversion of control). This means that if the World component needs some props that Hello doesn’t care about, it can be given those props somewhere else. We have therefore eliminated the problem of passing props through intermediary components.

Injecting props in redux

If we want to use redux, we need to also inject props at some point. This is done by a higher order component which is created by redux connect (Note however that connect doesn’t have to use the context API for this). The key is to treat this step as a configuration issue, and not involve the components in the process. So let’s have a look at configuration next.

Configuration (IOC container)

By using dependency injection we have eliminated some problems in the components themselves. The components can now be written without any awareness of redux or of props that need to be passed along. But how do we make all this happen? Each component that has dependencies in this system has a function that needs to be called with those dependencies. Who will make the calls? This is the perfect case for an IOC container.

Using the container we already know, it looks like this:

// MainPage.js
export default Profile =>
class MainPage extends Component{
render(){
<div>
<Profile />

Other stuff here...
</div>
}
}

// Profile.js
export default profileActionCreator =>
class Profile extends Component{
onClickTitle(){
profileActionCreator.loadProfileDetails()
}
render(){
<div>
<h1 onClick={this.onClickTitle.bind(this)}>
{this.props.profile.fullname}
</h1>
</div>
}
}

// createContainer.js
import {connect} from 'react-redux'
import Container from './Container'
import ProfileActionCreator from './ProfileActionCreator'
import Profile from './Profile'
import MainPage from './MainPage'

export default () => {
let c = new Container()

c.service('ProfileActionCreator', c => new ProfileActionCreator(c.Api))

c.service('Profile', c => connect(state => ({
profile: state.profile
}))(Profile(c.ProfileActionCreator)))

c.service('MainPage', c => MainPage(c.Profile))

return c
}

// index.js
// ...
import createContainer from './createContainer.'

let c = createContainer()
let MainPage = c.MainPage

ReactDOM.render(
<Provider store={store}>
<MainPage />
</Provider>,
document.getElementById('root')
)

In this example, we can notice a few things:

  • Our two components MainPage and Profile doesn’t know about redux
  • MainPage doesn‘t have to pass anything to Profile since it’s already connected to redux (but neither MainPage nor Profile depends on redux being setup)
  • Everything related to configuration of dependencies (including connected props) exists in its own location only to do with wiring up the application. This location can of course be split up into providers when necessary (see linked article on IOC. Not to be confused with provider components).
  • Although we use context in this example (via the store Provider), redux doesn’t actually need it. We could simply subscribe to the redux store directly if we wanted. The reason this would work is that the redux store doesn’t have to be a singleton in this setup, and any singleton-related issues with server rendering etc. are left moot
  • Other types of dependencies such as action creators, api calls, etc, can also be easily configured and used by the components
  • If we wanted to replace redux with mobx or similar, we could easily do so without modifying any components. Our “container” components have become simple one liners of configuration

When to NOT use DI/IOC in react?

The IOC container can easily be overused, so remember that it is there for a reason. It exists to help with configuration and dependencies (and in this case with connecting to redux). So use it for those things, and leave simple presentational components and similar to the regular import system.

My createContainer function is getting too large. Argh!

This one is pretty easy. Don’t put all configuration in one place. Split your app into modules, and use the IOC container provider pattern to hook up the different modules. Look out for another article explaining this as well :)