Understand the SOLID Principles in React

Understand the SOLID Principles in React

Get to know more about the SOLID Principles in React for Mobile app web app Development

If you are a developer or willing to step ahead in this domain, understand the basic thumb rule. Get a clear understanding of basic coding concepts & design principles. If you are well-versed in the basics, things will be easier.

If you searched the web regarding coding efficiency, every instructor and developer would advise only three significant aspects or Design principles.

  • Easy to maintain
  • Easy to extensible
  • Have an appropriate modularity

All these things learn over years of practice. You’ll make mistakes and fix them. Be patient, consistent, and efficient in writing simple code lines. Anytime anyone can associate with any project so the concerned person will take over charge of the project.

Suppose you are a React-Js developer and want to know more about improving your coding. Please keep remembering the SOLID Design principles.

SOLID Design Principles: 5 important rules in React

It is among the most common terms in the software industry and is popular among developers. Robert.C. Martin has proposed this concept to smoothen coding practices.

5 important rules in React.png

Single Responsibility Principle

We wrote a blog about testing strategies and mentioned this approach differently. Each test case should be centered on a specific unique problem. It will resolve any sort of ambiguity and duplicity.

This principle is focused on the appropriate code fragmentation. If you can design the front end with less code, what’s the need to create the pillar? Instead of this, you can develop functioning modules to do appropriate tasks. With that, you can call them through one line will save a lot of time.

You don’t need to write them repeatedly to perform single responsibility. This practice will make your code more robust, organized, and manageable. You don’t equip the trash code lines. Keep things brief and modularized.

const TodosPage = () => {
    const todos = useTodos();

    const renderTodos = () => {
        return todos.map(todo => {
            return <Todoitem id={todo.id} title={todo.title} />
        });
    };

    return (
        <div>
            <h1>My Todos:</h1>
            <ul>
                {renderTodos()}
            </ul>
        </div>
    )
};

const TodoItem = ({id, title}) => {
  return <li>{`ID: ${id}, Title: ${title}`}</li>
};

function useTodos(){
    const [todos, setTodos] = useState([]);

    useEffect(() => {
        async function getTodos() {
            const { data } = await axios.get("https://jsonplaceholder.typicode.com/todos");
            setTodos(data);
        };
        getTodos();
    }, []);

    return todos;
};

Open Closed Principle

A good developer only focuses on managing the functionality of existing code and making it extensible—practice composition instead of following the inheritance concept. You can flex the functionality of significant components by scaling specific properties, props & composition. We should not follow the idea of tight coupling and avoid the dependability of components on each other.

In React, we do so many amazing things, and this one is quite popular. Try this way to drive customizability.

const InputBox = ({stylesForH1, h1Message}) => {

  const [input, setInput] = useState("");

  return (
    <>
      <h1 style={stylesForH1}>{h1Message}</h1>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
    </>
  )
};

const FancyInputBox = () => {
  return (
    <div>
      <InputBox stylesForH1={{color: "red"}} h1Message={"Enter your name: "} />
    </div>
  )
};

What we have done here is that we create a generic <InputBox /> to style and use ‘props’ for the message. Then we create a <FancyInputBox>. Here we also render that generic <input /> to pass in props. We are using it to style the components.

Liskov Substitution Principle

However, LSP’s design principles needed less attention during the front-end development. But we should have a basic idea of this concept and principle.

If we state the Liskov Substitution Principle, it requires a single thing of substitutability if subclassed to its base class. However, this concept violates the React conceptualization design principles. A modern coding framework doesn’t follow the class concept, but it exists.

Interface Segregation Principle (ISP)

Try to avoid dependency on unuseful interfaces. Include only component props to carry the necessary functionality. The straightforward idea is to vanish the trash, and the high-level function should not influence implementation details. We want to see the user's data in the database while accomplishing another task from API.

In React:

const DisplayUser = ({name}) => {
  return (
    <div>
      <h1>Hello, {name}! </h1>
    </div>
  )
};

This <DisplayUser /> component only wants to know the user name instead of what the user is. But, In case, we will try to write the code like the following. It may be difficult because of the user’s name property.

const user = {
  name: "josh",
  age: 23,
  hairColor: "blonde",
  heightInCm: 175
};

Refactorization:

const user = {
  personalInfo: {
    name: "josh",
    age: 23
  },
  physicalFeatures: {
    hairColor: "blone",
    heightInC,: 175
  }
};

Here, props.user.name is undefined, which will encounter inconvenience. Instead of breaking <DisplayUser /> in such a format, you can only pass the value directly in <DisplayUser />.

const DisplayUser = ({name}) => {
  return (
    <div>
      <h1>Hello, {name}! </h1>
    </div>
  )
};

const App = () => {
  const user = {
    personalInfo: {
      name: "josh",
      age: 23
    },
    physicalFeatures: {
      hairColor: "blonde",
      heightInC,: 175
    }
  }
  return (
    <div>
      <DisplayUser name={user.personalInfo.name} />
    </div>
  )
};

We need to reduce the dependency on the parent component.

Dependency Inversion Principle (DIP)

DIP design principles: Follow the concept of abstraction with the high-level interface. Keep the details low-level, and manage connectivity through abstraction. We can see the function's real purpose in describing the component.

We are going to call an API to render the todos list.

const TodosPage = () => {
    const todos = useTodos();

    const renderTodos = () => {
        return todos.map(todo => {
            return <Todoitem id={todo.id} title={todo.title} />
        });
    };

    return (
        <div>
            <h1>My Todos:</h1>
            <ul>
                {renderTodos()}
            </ul>
        </div>
    )
};

As we can see <TodosPage> only interact with the useTodos() function. useTodos()abstract all the wiring. It manages readability. We are managing the high-level code structure closet ties with SRP. Here we are accessing the functionality, keeping function/components away. To illustrate the todos invocation, we don’t need to go into the <TododPage /> component.

function useTodos(){
    const [todos, setTodos] = useState([]);

    useEffect(() => {


        async function getTodosWithFetch() {
            const response = await fetch("https://jsonplaceholder.typicode.com/todos");
            const data = await response.json();
            setTodos(data);
        };
        getTodosWithFetch();
    }, []);

    return todos;
};

No need to modify the <TodosPage />. Further, proceed:

// useTodos.js
import localTodos from "./todos.json";
function useTodos(){
    const data = localTodos.todos;
    return todos;
};
export default useTodos;

// TodosPage.js
import useTodos from "./useTodos.js";

const TodosPage = () => {
    const todos = useTodos();

    const renderTodos = () => {
        return todos.map(todo => {
            return <Todoitem id={todo.id} title={todo.title} />
        });
    };

    return (
        <div>
            <h1>My Todos:</h1>
            <ul>
                {renderTodos()}
            </ul>
        </div>
    )
};

Here, useTodos() function to manage the invoking or fetching of all our todos without modifying the <TodosPage />. The todos.json file is a local file that reads the todos—no need to depend on API.

Abstraction layer Concept:

Here is a significant rule of the abstraction layer. According to this, each function line has the same level of abstraction. Also, that level is one step below the function name.

Things come from experience, and only an experienced developer/programmer can do this.

If we now abstract the functionality, the code will look something like this. We can’t understand things properly. It's pretty overwhelming.

const TodosList = () => {
  const [todos, setTodos] = useState([]);
  const [term, setTerm] = useState("");

  useEffect(() => {
      async function getTodos() {
          const { data } = await axios.get("https://jsonplaceholder.typicode.com/todos/");
          const filtered = data.filter(todo => todo.completed === false);
          const pattern = new RegExp(term, "g");
          const searched = filtered.filter(todo => pattern.test(todo.title));
          setTodos(searched);
      };
      getTodos();
  }, [term]);


  const renderTodos = () => {
      return todos.map(todo => {
          return (
              <li>
                  {`ID: ${todo.id}, Title: ${todo.title}`}
              </li>
          )
      });
  };

  return(
    <div>
    <input value={term} onChange={(e) => setTerm(e.target.value)} />
      <ul>
        {renderTodos()}
      </ul>
    </div>
  );
}

Solution:

const TodosList = () => {
  const [term, setTerm] = useState("");
  const todos = useTodos(term);

  const renderTodos = () => {
      return todos.map(todo => {
          return (
              <li>
                  {`ID: ${todo.id}, Title: ${todo.title}`}
              </li>
          )
      });
  };
  return(
    <div>
    <input value={term} onChange={(e) => setTerm(e.target.value)} />
      <ul>
        {renderTodos()}
      </ul>
    </div>
  );
};
function useTodos(term){
    const [todos, setTodos] = useState([]);

    useEffect(() => {
      async function getTodos() {
          const { data } = await axios.get("https://jsonplaceholder.typicode.com/todos/");
          const filtered = data.filter(todo => todo.completed === false);
          const pattern = new RegExp(term, "g");
          const searched = filtered.filter(todo => pattern.test(todo.title));
          setTodos(searched);
      };
      getTodos();
    }, [term]);

    return todos;
};

It looks manageable and readable. But here it is showing the useTodos() overloading. Network requests, pattern matching, and array manipular binding together in a single function seem rude.

function useTodos(term){
    const [todos, setTodos] = useState([]);

    useEffect(() => {
      async function getTodos() {
          const { data } = await axios.get("https://jsonplaceholder.typicode.com/todos/");
          const filteredAndMatchedTodos = filterAndMatchTodos(data, term);
          setTodos(filteredAndMatchedTodos);
      };
      getTodos();
    }, [term]);

    return todos;
};

function filterAndMatchTodos(todoList, searchTerm){
  const completedTodos = todoList.filter(todo => todo.completed === false);
  const pattern = new RegExp(searchTerm, "g");
  const matchingTodos = filtered.filter(todo => pattern.test(todo.title));
  return matchingTodos;
}

Now everything looks perfect and manageable as low-level filtering or regex matching work is done from useTodos(). It balances the desirable abstraction level. Now it can behave comfortably with network requests and a few React State management. filterAndMatchTodos() only look for the array of todos and filtration. It will proceed with a pattern match on a given search query.

Further, we can ease the following:

function filterAndMatchTodos(todoList, searchTerm){
  const completedTodos = filterTodoList(todoList);
  const matchingTodos = matchFilteredTodos(completedTodos, searchTerm); 
  return matchingTodos;
}
function filterTodoList(todoList){
  return todoList.filter(todo => todo.completed === false);
};
function matchFilteredTodos(compeltedTodos, searchTerm){
    const pattern = new RegExp(searchTerm, "g");
    const matchingTodos = compeltedTodos.filter(todo => pattern.test(todo.title));
    return matchingTodos;
};

A judgment call is required here to figure out the road map of purpose.

We hope this post may cover your doubts and give you a better idea to ease your coding experience.

Did you find this article valuable?

Support Quokka Labs' Blogs by becoming a sponsor. Any amount is appreciated!