DEV Community

Mohammad Faisal
Mohammad Faisal

Posted on • Edited on • Originally published at Medium

Apply the Dependency Inversion Principle in React

To read more articles like this, visit my blog

The dependency inversion principle is one of the famous SOLID principles. Also, it is one of the most important ones.

Today, we will see how to solve a very common mistake that novice React developers make using this principle.

I will try to keep it very simple. Let’s get started!

What Does This Principle Tell Us?

In terms of object-oriented programming, the main idea behind this principle is to always have a high-level code interface with abstraction rather than an implementation detail.

Hold on! I know what you are thinking: “I am a simple frontend developer. Why are you bothering me with these complex terms?”

Let me state it simply for you. For a React application, this principle means:

“No component or function should care about how a particular thing is done.”

Still not clear? OK, let’s get our hands dirty with some code!

A Practical Example

Let’s take a very common use case. We are going to make an API call from our component to get some data from a remote source. An implementation can look like this:

import React from "react";

const REMOTE_URL = 'https://jsonplaceholder.typicode.com/users'

export const Users = () => {

    const [users , setUsers] = useState([])

    useEffect(() => {

      fetch(URL)
        .then(response => response.json())
        .then(json => setUsers(json))

    },[])

    return <>
        <div> Users List</div>
        {filteredUsers.map(user => <div>{user.name}</div>)}
    </>
}
Enter fullscreen mode Exit fullscreen mode

Look at this component. It depends on some remote data that is fetched right inside the component.

Our Users component’s main responsibility is to render the data. It should not care about how data is fetched or where the data comes from.

This component knows too much — and that’s a problem.

Why?

Well, let’s say you have ten other components and all of them fetch their own data.

Now your manager comes along and tells you to use axios instead of fetch

You are in trouble! Now you have to go into each file and refactor the logic to use axios.

But life is not so simple! After a few days, your manager comes again and tells you to implement caching.

You have to do the same thing once again.

Thus, it increases the chance of introducing a bug in your software. Also, the code becomes unmaintainable and valuable time is wasted.

So What Should We Do Then?

Let’s introduce a data-fetching Hook and abstract away our logic outside our component because that’s exactly what this principle tells us. To depend on abstraction, remember?

import {useState} from "react";

export const useFetch = (URL) => {

    const [data , setData] = useState([])

    useEffect(() => {

        fetch(URL)
            .then(response => response.json())
            .then(json => setData(json))

    },[])

    return data;

}
Enter fullscreen mode Exit fullscreen mode

Now use this Hook inside our Users component:

import React from "react";
import useFetch from './useFetch'

const REMOTE_URL = 'https://jsonplaceholder.typicode.com/users'

export const Users = () => {

    const users = useFetch(REMOTE_URL)

    return <>
        <div> Users List</div>
        {filteredUsers.map(user => <div>{user.name}</div>)}
    </>
}
Enter fullscreen mode Exit fullscreen mode

Notice a great thing about this solution: Your useFetch Hook doesn’t care about who is calling it. It just takes a URL as an input and returns the data.

Now all other components can take advantage of the Hook that we just wrote. And our Users component no longer depends on the concrete details on how the data is coming back or which library is being used!

More Advanced Usage

Now let’s satisfy your manager with basic caching functionality:

import {useState} from "react";

export const useFetch = (URL) => {

    const [data , setData] = useState([])

    useEffect(() => {

      const cachedData = localstorage.getItem(URL)

      if(cachedData) {
        setData(JSON.parse(cachedData))
      }
      else{
        fetch(URL)
        .then(response => response.json())
        .then(json => setData(json))
      }

    },[URL])

    useEffect(() => {
      localstorage.setItem(URL , JSON.stringify(data))
    },[data])

    return users;

}
Enter fullscreen mode Exit fullscreen mode

You have to change the code in only one place now. That’s great! Let’s say you need to show API errors as a toast. Can you do that now? If so, then you got my point.

How To Detect It

  • In most cases, if you are violating the single-responsibility principle, then you might also be violating the dependency inversion principle.

  • For any component, look into the import section at the top. If you are importing some library that’s not responsible for displaying something (e.g. a toast or modal), then you might be violating the principle.

Previous Articles in this Series

  1. Single Responsibility Principle

  2. Open Closed Principle

  3. Liskov Substitution Principle

  4. Interface Segregation Principle

That’s all for today. I hope you enjoyed this article as well as this series.

Have something to say? Get in touch with me via LinkedIn or Personal Website

Top comments (1)

Collapse
 
9opsec profile image
9opsec

Very useful code but you need to use this as the import:

import {useState, useEffect} from "react";