Best Coding Practices In ReactJS

Introduction

In this article, we will discuss the best coding practices that you can follow in your next project.

These practices will make your code, 

  1. reusable
  2. cleaner
  3. efficient and
  4. easily adaptable by another developer.

List of coding practices that you should follow.

  1. Combine state
  2. In long component hierarchy use useContext
  3. Separate UI and logic
  4. Remove unnecessary props
  5. Write a function for a repetitive task
  6. Avoid named import/member import
  7. Use forEach instead of map

1. Combine state

In most of the components, you'll have a state. While defining a new state, take time and think if you can combine multiple states into a single state. Let's understand this with the help of an example.

Let's say you are working on a chocolate website.

You have two types of sizes.

  1. Default size ---> You will receive sizes from API
  2. Custom size ---> User can add custom sizes.

Once user has added custom sizes, user can proceed for checkout by selecting desired sizes. 

In the wrong coding practice, you can have three different states.

  1. state for default sizes (received from backend)
  2. state for custom sizes
  3. state for selected sizes

So you will define three different states.

const [APISizes, setAPISizes] = useState([{
    id: ''
    height: '',
    width: ''
}]);
const [customSizes, setCustomSizes] = useState([{
    id: '',
    height: '',
    width: ''
}]);
const [selectedSizes, setSelectedSizes] = useState([1, 2]);

Now you have to keep an eye on three different states and you'll need to keep them in sync. Let's look at scenarios where it will create a problem.

  1. While displaying all sizes you have to loop through two states. (on APISizes and customSizes)
  2. In selectedSizes we are storing only ids. For size information we have to iterate over APISize and CustomSize.

In good coding practice, you can define a single state as follow.

const [userSuggestions, setUserSuggestion] = useState([{
    id: 1,
    height: 100,
    width: 100,
    isCustom: false,
    isSelected: false
}]);

In this case, you have to think about only one state. If another developer works on your code it's easy for her/him too.

In this coding practice, If you want to introduce a new key, you have to update only one state instead of 2-3 states.

const [userSuggestions, setUserSuggestion] = useState([{
    id: 1,
    height: 100,
    width: 100,
    isCustom: false,
    isSelected: false,
    isByDefaultSelected: true,
}]);

2. In long component hierarchy use useContext

In a long component hierarchy, useContext will provide cleaner and reusable code. Look at the following example.

In the application, you can select a project and folder. In the dashboard component, we want to show the total selected projects and folders. We need to define two states in the dashboard component

  1. Selected project
  2. Selected folder

We will pass these states from

Selected project: Dashboard -> Projects -> Project Display -> Project Option

Selected folder: Dashboard -> Folders -> Folder display -> Folder Option

Best coding practices in react

This becomes nastier as the number of state and the number of components grows.

The solution to this problem is creating context. It will allow you to use state in any component. You will call context from the topmost component and all children will be able to use state.

If you don't know how context works, you can go through this article: https://reactjs.org/docs/context.html

3. Separate logic and UI

Prefer to separate logic and UI. For example, you can write onClick function as an inline function or you can call a separate function. Create a separate function rather than writing an inline function. This will give a clear separation between UI and logic. It will make the code more understandable, clear, and reusable.

4. Remove unnecessary props

Keep a good eye on whether your props are dynamic or static. Other than static props, sometimes we pass redux state as props which don't help in reducing number of render at all. Passing redux state as props makes component difficult to reuse. We will understand this with help of an example.

In our project we have profile component. Profile component is calling the Image component. Image component requires login user information and login user information is stored in redux state.

Profile component is already calling a redux state of login information. In this situation, you can choose from two options.

  1. Pass redux state as props from parent component(Profile) to child component(Image)
  2. Call redux state in child component(Image) using useSelector

You should always go for the second option as in the future Image component will be used from multiple components. All the parent component of Image component have to call login user information. (As login user information is compulsory props of Image component). This will lead to an unnecessary call of redux state every time the component is reused.

In both cases,

  1. If you pass state as props from parent component (From profile to Image)
  2. use useSelector inside child component (Image component)

react will re-render. A change in props causes re-render and change in the redux state also cause re-render.

5. Write a function for a repetitive task

This seems to be a normal thing but keep a good eye on repetitive code. For example, you might be updating the same state in the same manner from 5 different components. In this case, create one function to update state and use that function in all the components. Slow down while you write code and if you encounter yourself writing the same code, again and again, write a common function instead. I'll highly recommend creating a common function for the repetitive task. As more code you'll write, you will appreciate the time you have spent writing common functions. In the future, if you have any code change, there will be only one place to change code rather than going through all the components.

6. Avoid named import/member import if possible

First, let's understand how we can import the module. Let's say you are using material UI. In your component, you need Button and TextField from material UI. You can import them in two ways.

a. Named import / Member import

import {TextField, Button} from "@mui/material";

b. Default import

import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";

Always prefer default import as in the default import only code of button and textfield is imported. In the named import/member import, all the modules of material UI are loaded. From all the material UI code you are using button and textfield in your component. Over time this will increase bundle size. Default import have one disadvantage. If you are using 10 different components of material UI, you'll have 10 different imports in a file.

import CloseIcon from "@mui/icons-material/Close";
import AppBar from "@mui/material/AppBar";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Container from "@mui/material/Container";
import IconButton from "@mui/material/IconButton";
import Paper from "@mui/material/Paper";
import Popover from "@mui/material/Popover";
import TextField from "@mui/material/TextField";
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";

It will increase the line of code but it will reduce bundle size

7. Use forEach instead of map

In code, we normally use a map to iterate over an object. Many developers follow this wrong practice. If you only want to iterate over an object you must use forEach. If you want to modify the current object then use a map.

Let's understand this with an example. Let's say we have sizes object as follows.

sizes = {
    category: '',
    height: '',
    width: '',
    isSelected: false
}

a. forEach

We want to iterate over sizes object to get all type of size categories into one array.

We are not modifying sizes object but we are iterating over sizes object to get new information.

const allCategory = [];
sizes.forEach((sizeObj) => {
    const {
        category
    } = sizeObj;
    if (!allCategory.includes(category)) allCategory.push(category);
});

b. map

On button click, we want to select all 'custom' category sizes. In this case, we want to modify the sizes object.

const updatedSizes = sizes.map((sizeObj) => {
    const {
        category
    } = sizeObj;
    if (category === 'custom') {
        const newSizeObj = {
            ...sizeObj,
            isSelected: true,
        };
        return newSizeObj;
    }
    return sizeObj;
});

map returns a new object so in updatedSizes, all 'personal' category sizes will be selected.

Following are some of the variable-related practices that you can follow while coding.

  1. Use const instead of let (if possible)
  2. Write the meaningful and more understandable variable name