React Native – Is Using a Singleton the Best Alternative to Dependency Injection?

Architecturedependency-injectiondesign-patternsreactsingleton

I've been reading a lot about the singleton pattern and how it's "bad" because it makes the classes using it hard to test so it should be avoided. I've read some articles explaining how the singleton could be replaced with dependency injection, but it seems unnecessarily complex to me.

Here's my problem in a bit more detail. I'm building a mobile app using React Native and I want to make a REST client that will communicate with the server, get data, post data and handle the login (store the login token and send it with each request after login).

My initial plan was to create a singleton object (RESTClient) that my app will use initially to login and then make request sending the credentials where needed. The DI approach seems really complicated to me (maybe because I never used DI before) but I'm using this project to learn as much as can so I want to do what's best here. Any suggestions and comments are much appreciated.

Edit:
I've now realized I've worded my question poorly. I wanted some guidance on how to avoid the singleton pattern in RN and should I even do it. Luckily Samuel gave me just the kind of answer I wanted. My problem was I wanted to avoid the singleton pattern and use DI, but it seemed really complicated to implement it in React Native. I did some further research and implemented it using Reacts context system.

For anyone interested here's how I did it. Like I said, I used the context in RN which is something like props but it's propagated down to every component.

In the root component I provide the needed dependencies like this:

export default class Root extends Component {
  getChildContext() {
    restClient: new MyRestClient();
  }

  render() {...}
}
Root.childContextTypes = {restClient: PropTypes.object};

Now restClient is available in all components below Root. I can access it like this.

export default class Child extends Component {
  useRestClient() {
    this.context.restClient.getData(...);
  }

  render() {...}
}
Child.contextTypes = {restClient: PropTypes.object}

This effectively moves object creation away from logic and decouples the REST client implementation from my components.

Best Answer

Dependency injection does not need to be complex at all, and it's absolutely worth learning and using. Usually it's complicated by the usage of dependency injection frameworks, but they aren't necessary.

In its simplest form, dependency injection is passing dependencies instead of importing or constructing them. This can be implimented by simply using a parameter for what would be imported. Lets say you have a component named MyList that needs to use RESTClient to fetch some data and display it to the user. The "singleton" approach would look something like this:

import restClient from '...' // import singleton

class MyList extends React.Component {
  // use restClient and render stuff
}

This tightly-couples MyList to restClient, and there is no way you can unit test MyList without testing restClient. The DI approach would look something like this:

function MyListFactory(restClient) {
  class MyList extends React.Component {
    // use restClient and render stuff
  }

  return MyList
}

That's all it takes to use DI. It adds at most two lines of code and you get to eliminate an import. The reason why I introduced a new function "factory" is because AFAIK you can't pass additional constructor parameters in React, and I prefer not to pass these things down through React properties because it's not portable and all parent components must know to pass all props to children.

So now you have a function to construct MyList components, but how do you use it? The DI pattern bubbles up the dependency chain. Say you have a component MyApp that uses MyList. The "singleton" approach would be:

import MyList from '...'
class MyApp extends React.Component {
  render() {
    return <MyList />
  }
}

The DI approach is:

function MyAppFactory(ListComponent) {
  class MyApp extends React.Component {
    render() {
      return <ListComponent />
    }
  }

  return MyApp
}

Now we can test MyApp without testing MyList directly. We could even reuse MyApp with a completely different kind of list. This pattern bubbles up to the composition root. This is where you call your factories and wire all the components.

import RESTClient from '...'
import MyListFactory from '...'
import MyAppFactory from '...'

const restClient = new RESTClient(...)
const MyList = MyListFactory(restClient)
const MyApp = MyAppFactory(MyList)

ReactDOM.render(<MyApp />, document.getElementById('app'))

Now our system uses a single instance of RESTClient, but we've designed it in a way that components are loosely-coupled and easy to test.

Related Topic