ReactJS Bad Practices and how to avoid them

Share your love

Every developer wants to write clean and concise code for his apps so that he does not face any issues while debugging them.

But still, sometimes we get tempted or honey-trapped and make some pretty common mistakes that are not recommended or are categorized as anti-pattern or bad practices in React which should be avoided at all costs.

Otherwise, we will have to face some serious performance issues later in the development process.

In this blog, we will discuss some ReactJS Bad Practices that developers do, and how to avoid them.

Index

  1. Using Index as the key in the map function
  2. Polluting Render method by using Anonymous functions
  3. Using Nested Components
  4. Nesting Ternary Operator in Render
  5. Not Destructuring Props 
  6. Prop Drilling
  7. Not cleaning up Event Listeners
  8. Using Inline CSS
  9. Using Divs everywhere

Let’s start…

1. Using Index as the Key in the map() function

The map() function is used to print all the elements of an array into a new array by calling a function for each element.

In react, the map() function requires a key to distinguish between each element and to detect their exact changes.

According to the official documentation, ‘A key is a special string attribute you need to include while creating lists of elements. Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.’

Problem

Using the index of an array as the key for the map() function is not recommended as there can be a change in the order of elements if we perform any operation like addition, deletion, etc on them.

Because of this, React will not be able to detect the exact changes in the state which can cause some serious performance issues.

Example

Suppose we have a list of 5 elements with key as the index 

<ul>
  <li key={1}>Milk</li>
  <li key={2}>Eggs</li>
  <li key={3}>Food</li>
  <li key={4}>Bread</li>
  <li key={5}>Sausage</li>
</ul>;

Now, in this case, there is a state change like adding a new item, deleting an item, etc, React just iterates over each list in both the cases and updates the React DOM with only the state that has some changes in it (Virtual DOM concept).

Let’s say, we have added an item at the end of the list. As there is no change in the order of the items, React will only render once to add the extra item at the end.

<ul>
  <li key={1}>Milk</li>
  <li key={2}>Eggs</li>
  <li key={3}>Food</li>
  <li key={4}>Bread</li>
  <li key={5}>Sausage</li>
  <li key={6}>Butter</li>
</ul>;

But what if we have to add an item at the beginning or in the middle of the list. 

This time, there will be a change in the order of each item, and because of that React will re-render all the elements again and not the one that has been added.

<ul>
  <li key={1}>Butter</li>
  <li key={2}>Milk</li>
  <li key={3}>Eggs</li>
  <li key={4}>Food</li>
  <li key={5}>Bread</li>
  <li key={6}>Sausage</li>
</ul>;

Solution

This can be very easily avoided by using a unique id. Let’s take the same example again but this time the key will have a unique value for every item.

<ul>
  <li key={"1a"}>Milk</li>
  <li key={"2b"}>Eggs</li>
  <li key={"3c"}>Food</li>
  <li key={"4d"}>Bread</li>
  <li key={"5e"}>Sausage</li>
</ul>;

Now even if we add elements at the beginning or end, we won’t face an issue since keys are different and it has nothing to do with the index of the array.

Since, React tracks all list items with their key attribute, after adding a new element it would not re-render the previous list items.

When is it safe to use the index as the key for the map() function

We can only use the index as a key when – 

  1. Data is stable
  2. Operations like addition, deletion. Sorting, etc will not take place
  3. We don’t have a stable id for rendered items

2. Polluting Render Method by using Anonymous Functions

To understand this, let’s take an example

import React from "react";
 
const App = () => {
  const handleClick = () => {
    console.log("You Clicked???????");
  };
  return <button onClick={() => handleClick()}>Click me</button>;
};
 
export default App;
 

There is no problem with this code, and it’s also giving us our desired output as shown

ReactJS Bad Practices
Polluting Render Method by using Anonymous Functions

Then, why is it not recommended?

Problem

The problem with this syntax is that a different callback is created each time the Button renders. 

In most cases, this is fine. However, if this callback is passed as a prop to lower, there will be many extra re-renders.

What it means is, by passing an anonymous function, React will always re-render itself since it receives a new anonymous function as a prop which it is unable to compare to the previous anonymous function as both of them are anonymous.

Solution

We can use the binding in the constructor or using the class fields syntax, or simply pass the function as an event handler to avoid this sort of problem. 

By doing this we are telling React that nothing is changed, so that unnecessary re-renders can be avoided.

3. Nested Components

React has given us the ability to divide one huge component into countless small components, and link them with the help of props which has made our code cleaner and more understandable. 

But sometimes, we get tempted and avoid the use of props. We declare both parent and the child component in the same component as shown

import React, { useState } from "react";
 
const Main = () => {
  const [name, setName] = useState("Ateev");
 
  const Sub = () => {
    return <h1 className="p-5">Hello {name}, I am the child of Mr & Mrs Khana</h1>;
  };
  return (
    <>
      <Sub />
    </>
  );
};
 
export default Main;

There is nothing wrong with defining both our parent and the child component under the same hood, and the app will also work fine, but there will be some serious performance issues with our app.

Problem

We will receive performance issues because every time our Main Component gets rendered, the SubComponent also gets rendered, and this goes on for infinity

Solution

By passing props we can solve this issue very easily as now we are telling React until and unless there is a change in the prop, we don’t want the Sub component to be rendered. 

4. Nesting Ternary Operator in Render

It is a good practice to use the ternary operator in place of conditional statements as it makes the code clean and concise and also takes less time to write than if-else statements, but excess of everything is bad, right!

Problem

The problem here is Nesting the ternary operator will cause issues like readability, which makes it harder to debug later on.

Consider the following code and do tell me if you can understand the code at the first glance-

import React, { useState } from "react";
import Data from "./Data";
import NoData from "./NoData";
 
const MainComponent = () => {
  const [isAccepted, setIsAccepted] = useState();
  const [isNotAccepted, setIsNotAccepted] = useState();
 
  return (
    <>
      <div>
        {isAccepted ? (
          <h1>Hello Ateev </h1>
        ) : isNotAccepted ? (
          <Data />
        ) : (
          <NoData />
        )}
      </div>
    </>
  );
};
 
export default MainComponent;

In the above code, we have only used the ternary operator twice, and still, it may sometimes be difficult to understand it at first glance.

Now, if there were more than 5 ternary operators nested in the same way (I am using Prettier though to make it look readable but that also won’t work if we were to nest 5 or more ternary operators), we will not be able to read and understand the conditions and their result to debug it properly.

Solution

In situations like these, it is better to use conditional statements as they are better to understand and give the reader what they are looking for at first glance which will then be easier to debug as well.

import React, { useState } from "react";
import Data from "./Data";
import NoData from "./NoData";
const MainComponent = () => {
  const [isAccepted, setIsAccepted] = useState();
  const [isNotAccepted, setIsNotAccepted] = useState();
 
  const Employee = () => {
    if (isAccepted) {
      return <h1>Hello Ateev</h1>;
    } else if (isNotAccepted) {
      return <Data />;
    } else {
      <NoData />;
    }
  };
  return (
    <>
      <div>{Employee}</div>
    </>
  );
};
 
export default MainComponent;

According to StackOverflowJSX is fundamentally syntactic. After compilation, JSX expressions become regular JavaScript function calls and evaluate JavaScript objects. We can embed any JavaScript expression in JSX by wrapping it in curly braces.

This means that we cannot directly use statements like if-else, switch, for, etc in JSX, they can only be used outside of the return function as shown in the above code.

5. Not Destructuring Props and States

We are familiar with the concept of props, and how they play the important role in making the flow of our React apps dynamic. 

This is just a personal opinion, but not destructuring props can make the code unreadable at some point and also waste a lot of time as we have to write props.state every time we use props in the code, which can be utilized doing something else.

Consider an example – 

import React from "react";
const MainComponent = (props) => {
  return (
    <>
      <div>
         Hi, {props.name}. Welcome to {props.company}, you can start working from {props.date}
      </div>
    </>
  );
};
 
export default MainComponent;

It does not make a big difference in such a small component, but in a React component with dozens of props, destructuring will affect the readability of the code.

Solution

The solution to this problem lies in the point itself – destructuring of props.

Here is the same code as above but after destructuring props

import React from "react";
const MainComponent = ({ name, company, date }) => {  // destructuring
  return (
    <>
      <div>
        Hi, {name}. Welcome to {company}, you can start working from {date}
      </div>
    </>
  );
};
 
export default MainComponent;

It is as clear as a day that not only does it take less time to write about writing like this, it has increased its readability and has become more understandable.

Specifying the props explicitly with destructuring is also a reminder to yourself and other developers which props you are expecting.

And sometimes it is very easy to lose track of them when not destructed.

6. Prop Drilling

Many of us ‘developers’ are aware of this problem as we continue working in React, but if you are not aware of this problem then do read this carefully.

Consider an example, we are working on a project which has many nested child components, and we want to use a certain prop in the last child component or any other child component in the middle, but we have declared the state and also used it in the initial parent component.

Problem

Now, for no reason, we have to define that state as prop countless times until we have reached the desired point where we have to use it. 

This process is called prop drilling and consumes a lot of memory and time.

import React, { useState } from "react";
 
function Parent() {
  const [fName, setfName] = useState("firstName");
  const [lName, setlName] = useState("LastName");
  return (
    <>
      <div>This is a Parent component</div>
      <br />
      <Child1 fName={fName} lName={lName} />
    </>
  );
}
 
function Child1({ fName, lName }) {
  return (
    <>
      This is Child 1 Component.
      <br />
      <Child2 fName={fName} lName={lName} />
    </>
  );
}
function Child2({ fName, lName }) {
    return (
      <>
        This is Child 2 Component.
        <br />
        <Child3 fName={fName} lName={lName} />
      </>
    );
  }
 
.
.
.
.
.
function Child6({ fName, lName }) {
  return (
    <>
      This is Child 6 Component.
      <br />
      <Child7 fName={fName} lName={lName} />
    </>
  );
}
 
function Child7({ fName, lName }) {
  return (
    <>
      This is Child7 component.
      <br />
      <h3> Data from Parent component is as follows:</h3>
      <h4>{fName}</h4>
      <h4>{lName}</h4>
    </>
  );
}
 
export default Parent;

In the above code, we have defined two states fName and lName in the parent component and used them in the last child component by defining them as props in every child component but not using them there.

Solution  

One way to deal with this situation is by Redux, but it is rather hard to deal with, and the other one is context API or the useContext hook which is the right choice considering it is easy to understand and light-weighted than redux.

The useContext hook is based on the context API and works on the mechanism of Provider and Consumer. 

Provider wraps the component and sends it to the DOM tree, which can be accessed and used by any child component through the useContext hook no matter how deep it is.

Let’s take the same example but this time with useContext hook – 

import React, { useState, useContext } from "react";
let context = React.createContext(null);
function Parent() {
const [fName, setfName] = useState("firstName");
const [lName, setlName] = useState("LastName");
return (
    <context.Provider value={{ fName, lName }}>
    <div>This is a Parent component</div>
    <br />
    <Child1 />
    </context.Provider>
);
}
 
function Child1() {
return (
    <>
    This is Child1 Component.
    <br />
    <Child2 />
    </>
);
}
 
function Child2() {
return (
    <>
    This is ChildB Component.
    <br />
    <ChildC3/>
    </>
);
}
.
.
.
.
.
function Child6() {
  return (
    <>
      This is Child 6 Component.
      <br />
      <Child7 />
    </>
  );
}
 
function Child7() {
const { fName, lName } = useContext(context);
return (
    <>
    This is Child7 component.
    <br />
    <h3> Data from Parent component is as follows:</h3>
    <h4>{fName}</h4>
    <h4>{lName}</h4>
    </>
);
}
 
export default Parent;

Now, as we can see we have our two states fName and lName defined in the parent component and used in the last child component without passing them down the tree like in the first case we did – read the official documentation for more details.

Context API is something that should not be used continuously because it makes the components impossible to reuse.

7. Not Cleaning up Event Listeners

An event is something that a user does or the browser does like clicking a button or resizing a window and an event listener is set up when we write code that defines the kind of event that might occur and that code should run when that event is triggered.

Problem

Callbacks are defined as listening to the events over time. But a time will eventually come when they will stop listening, and at that time they’re not needed anymore, thus should be cleaned up.

This is because keeping event listeners around isn’t free. It takes memory and some processing power from the browser and the host computer.

In React, the component will often re-render which will trigger the rendering of event listeners as well. This will create an error as this re-rendering will create multiple listeners which we don’t want which will result in a memory leak.

Solution

We know that cleanups are necessary to avoid memory leaks and can be done very easily just by adding a class in our component where the unmount startsremoveEventListner.

See the below to understand this 

import React, { useEffect } from "react";
 
const MainComponent = () => {
  useEffect(() => {
    // If only left like this, there will be memory leak
    document.addEventListener("click", onClick);
 
    return () => {
        //good practice
      document.removeEventListener("click", onClick);
    };
  }, []);
 
  return (
    <>
      <div>Hello</div>
    </>
  );
};
 
export default MainComponent;

There are two types of cleanups – 

1. Automatic Cleanup – in this, the cleanup process is automatically done by React. 

import React from "react";
 
const App = () => {
  const handleClick = () => {
    console.log("You Clicked???????");
  };
  return <button onClick={() => handleClick()}>Click me</button>;
};
 
export default App;

2. Manual Cleanup – the above is the perfect example of manual clean as we are manually adding an event listener thus we have to remove it manually also

8. Using Inline CSS

CSS plays a vital role in styling our App. There are many ways in which we can add CSS to our App like CSS in JS, Inline CSS, and separate CSS files.

But among them, using inline CSS has some disadvantages  – 

  1. We cannot use hover, focus, placeholder, after, before, etc selectors
  2. We can’t use media queries for making our app responsive
  3. It is not advised to use inline CSS for large applications as the app may face some performance issues in the long run.
  4. It makes debugging our apps much more difficult as using inline CSS may make our code complex to understand

What we can do is use either make a separate CSS file like it was with HTML or use CSS in JS by making a separate style component in the component only like this – 

import React from "react";
 
const App = () => {
  const styles = {
    width: 200,
    height: 50,
    backgroundColor: "red",
  };
 
  return (
    <>
      <button style={styles}>My Button</button>
    </>
  );
};
export default App;

9. Divs Everywhere

We all know how important it is to use Semantics for SEO purposes while making an app, but we often forget to use them and use only divs as we know we can style our component or tag easily using CSS or its frameworks. 

Semantics is one thing, we even forget to use p, h1, span, etc, tags that make the structure of the app.

We should use tags like nav, footer, article, section to give the search engine something to crawl and differentiate between sections of our app.

Conclusion

In this blog, we have discussed the most common bad practices that React Developers do – more often done by beginners, which if avoided will surely have an impact on the performance of the app in the long run

Share your love

Leave a Reply