React Best Practices Handbook - Part II

Let's explore more best practices of React.

Clear Flow of execution

Having a clear flow of execution is essential for writing clean code because it makes the code easier to read, understand, and maintain. Code that follows a clear and logical structure is less prone to errors, easier to modify and extend, and more efficient in terms of time and resources.

On the other hand, spaghetti is a term used to describe code that is convoluted and difficult to follow, often characterized by long, tangled, and unorganized code blocks. Spaghetti can be the result of poor design decisions, excessive coupling, or lack of proper documentation and commenting.

Here are two examples of javascript code that perform the same task, one with a clear flow of execution, and the other with spaghetti code.

// Example 1: Clear flow of execution
function calculateDiscount(price, discountPercentage) {
  const discountAmount = price * (discountPercentage / 100);
  const discountedPrice = price - discountAmount;
  return discountedPrice;
}

const originalPrice = 100;
const discountPercentage = 20;
const finalPrice = calculateDiscount(originalPrice, discountPercentage);

console.log(finalPrice);

// Example 2: Spaghetti code
const originalPrice = 100;
const discountPercentage = 20;

let discountedPrice;
let discountAmount;
if (originalPrice && discountPercentage) {
  discountAmount = originalPrice * (discountPercentage / 100);
  discountedPrice = originalPrice - discountAmount;
}

if (discountedPrice) {
  console.log(discountedPrice);
}

As we can see, example 1 follows a clear and logical structure, with a function that takes in the necessary parameters and returns the calculated result. On the other hand, example 2 is much more convoluted, with variables declared outside of any function and multiple statements used to check if the code block has been executed successfully.

Reusability

Code reusability is a fundamental concept in software engineering that refers to the ability of code to be used multiple times without modification.

The importance of code reusability lies in the fact it can greatly improve the efficiency and productivity of software development by reducing the amount of code that needs to be written and tested.

By reusing existing code, developers can save time and effort, improve code quality and consistency, and minimize the risk of introducing bugs and errors. Reusable also allows for more modular and scalable software architectures, making it easier to maintain and update codebases over time.

// Example 1: No re-usability
function calculateCircleArea(radius) {
  const PI = 3.14;
  return PI * radius * radius;
}

function calculateRectangleArea(length, width) {
  return length * width;
}

function calculateTriangleArea(base, height) {
  return (base * height) / 2;
}

const circleArea = calculateCircleArea(5);
const rectangleArea = calculateRectangleArea(4, 6);
const triangleArea = calculateTriangleArea(3, 7);

console.log(circleArea, rectangleArea, triangleArea);

This example defines three functions that calculate the area of the circle, rectangle, and triangle, respectively. Each function performs a specific task, but none of them are reused for other similar tasks.

The code is inefficient since it repeats the same logic multiple times.

// Reusable function for calculating area of geometric shapes
function calculateArea(shape, ...args) {
  switch (shape) {
    case 'circle':
      const [radius] = args;
      const PI = 3.14;
      return PI * radius * radius;
    case 'rectangle':
      const [length, width] = args;
      return length * width;
    case 'triangle':
      const [base, height] = args;
      return (base * height) / 2;
    default:
      throw new Error(`Shape "${shape}" not supported.`);
  }
}

// Example usage
const circleArea = calculateArea('circle', 5); // Calculate area of circle with radius 5
const rectangleArea = calculateArea('rectangle', 4, 6); // Calculate area of rectangle with length 4 and width 6
const triangleArea = calculateArea('triangle', 3, 7); // Calculate area of triangle with base 3 and height 7

console.log('Circle Area:', circleArea);
console.log('Rectangle Area:', rectangleArea);
console.log('Triangle Area:', triangleArea);

This example defines a single function calculateArea that takes an shape argument and a variable number of arguments. Based on the shape argument, the function performs the appropriate calculation and returns the result.

This approach is much more efficient since it eliminates the need to repeat code for similar tasks. It is also more flexible and extensible, as additional shapes can easily be added in the future.

Conciseness vs Clarity

When it comes to writing clean code, it's important to strike a balance between conciseness and clarity. While it's important to keep code concise to improve its readability and maintainability, it's equally important to ensure that the code is clear and easier to understand. Writing overly concise code can lead to confusion and errors, and can make the code difficult to work with other developers.

Here are two examples that demonstrate the importance of conciseness and clarity.

// Example 1: Concise function
const countVowels = s => (s.match(/[aeiou]/gi) || []).length;
console.log(countVowels("hello world"));

This example uses a concise arrow function and regex to count the number of vowels in a given string. When the code is very short and easy to write, it may not immediately clear to other developers how the regex pattern works, especially if they are not familiar with regex syntax.

// Example 2: More verbose and clearer function
function countVowels(s) {
  const vowelRegex = /[aeiou]/gi;
  const matches = s.match(vowelRegex) || [];
  return matches.length;
}

console.log(countVowels("hello world"));

The example uses the traditional function and regex to count the number of vowels in a given string but does so in a way that is clear and easy to understand. The function name and the variable name are descriptive, and the regex pattern is stored in a variable with a clear name. This makes it easy to see what the function is doing and how it works.

It's important to strike a balance between conciseness and clarity when writing code. While concise code can improve readability and maintainability, it's important to ensure that the code is still clear and easy to understand for other developers who may be working with the codebase in the future.

By using descriptive functions and variable names and making use of clear and readable code formatting and comments, it's possible to write clean and concise code that is easy to understand and work with.

Single responsibility principle

The single responsibility principle (SRP) is a principle in software development that states that each class or module should have only one reason to change, in other words, each entity in our codebase should have only one responsibility.

This principle helps to create code that is easy to understand, maintain, and extend.

By applying SRP, we can create code that is easier to test, reuse, and refactor, since each module only handles a single responsibility. This makes it less likely to have side effects or dependencies that make the code harder to work with.

// Example 1: Withouth SRP
function processOrder(order) {
  // validate order
  if (order.items.length === 0) {
    console.log("Error: Order has no items");
    return;
  }

  // calculate total
  let total = 0;
  order.items.forEach(item => {
    total += item.price * item.quantity;
  });

  // apply discounts
  if (order.customer === "vip") {
    total *= 0.9;
  }

  // save order
  const db = new Database();
  db.connect();
  db.saveOrder(order, total);
}

In this example, the processOrder function handles several responsibilities, it validates the order, calculates the total, applies discounts, and saves the order to a database. This makes the function long and harder to understand and any changes to one responsibilities may affect the others, making it harder to maintain.

// Example 2: With SRP
class OrderProcessor {
  constructor(order) {
    this.order = order;
  }

  validate() {
    if (this.order.items.length === 0) {
      console.log("Error: Order has no items");
      return false;
    }
    return true;
  }

  calculateTotal() {
    let total = 0;
    this.order.items.forEach(item => {
      total += item.price * item.quantity;
    });
    return total;
  }

  applyDiscounts(total) {
    if (this.order.customer === "vip") {
      total *= 0.9;
    }
    return total;
  }
}

class OrderSaver {
  constructor(order, total) {
    this.order = order;
    this.total = total;
  }

  save() {
    const db = new Database();
    db.connect();
    db.saveOrder(this.order, this.total);
  }
}

const order = new Order();
const processor = new OrderProcessor(order);

if (processor.validate()) {
  const total = processor.calculateTotal();
  const totalWithDiscounts = processor.applyDiscounts(total);
  const saver = new OrderSaver(order, totalWithDiscounts);
  saver.save();
}

In this example, the processOrder function has been split into two classes that follow the SRP: OrderProcessor and OrderSave .

The OrderProcessor class handles the responsibilities of validating the order, calculating the total, and applying discounts, while the OrderSaver class handles the responsibilities of saving the order to the database.

This makes the code easier to understand, test, and maintain since each class has a clear responsibility and can be modified or replaced without affecting others.

Having a "Single Source of Truth"

Having a "single source of truth" means there is only one place where a particular piece of data or configuration is stored in the codebase, and any references to it in the code refer back to that one source. This is important because it ensures that the data is consistent and avoids duplication and inconsistency.

Here is an example to illustrate the concept. Let's say we have an application, that needs to display the current weather conditions in a city. We could implement this feature in two different ways.

// Option 1: No "single source of truth"

// file 1: weatherAPI.js
const apiKey = '12345abcde';

function getCurrentWeather(city) {
  return fetch(`https://api.weather.com/conditions/v1/${city}?apiKey=${apiKey}`)
    .then(response => response.json());
}

// file 2: weatherComponent.js
const apiKey = '12345abcde';

function displayCurrentWeather(city) {
  getCurrentWeather(city)
    .then(weatherData => {
      // display weatherData on the UI
    });
}

In this option, the API key is duplicated in two different files, making it harder to maintain and update. If we ever need to change the API key, we have to remember to update it in both places.

// Option 2: "Single source of truth"

// file 1: weatherAPI.js
const apiKey = '12345abcde';

function getCurrentWeather(city) {
  return fetch(`https://api.weather.com/conditions/v1/${city}?apiKey=${apiKey}`)
    .then(response => response.json());
}

export { getCurrentWeather, apiKey };


// file 2: weatherComponent.js
import { getCurrentWeather } from './weatherAPI';

function displayCurrentWeather(city) {
  getCurrentWeather(city)
    .then(weatherData => {
      // display weatherData on the UI
    });
}

In this option, the API key is stored in one place(in the weatherAPI.js file) and exported for other modules to use. This ensures there is only one source of truth for the API key and avoids duplication and inconsistency.

If we ever need to update the API key, we can do it in one place and all other modules that use it will automatically get the updated value.

Only Expose and consume the data you need

One important principle of writing clean code is to only expose and consume the information that is necessary for a particular task. This helps to reduce complexity, increase efficiency, and avoid errors that can arise from using unnecessary data.

When data is not needed to expose or consume, it can lead to performance issues and make the code more difficult to understand and maintain.

Suppose you have an object with multiple properties, but you only need to use a few of them. One way to do this would be to reference the object and the specific properties every time you need them. And this can become verbose and error-prone, especially if the object is deeply nested. A cleaner and more efficient solution would be to use object-destructing to only expose and consume the data you need.

// Original object
const user = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  age: 25,
  address: {
    street: '123 Main St',
    city: 'Anytown',
    state: 'CA',
    zip: '12345'
  }
};

// Only expose and consume the name and email properties
const { name, email } = user;

console.log(name); // 'Alice'
console.log(email); // 'alice@example.com'

Modularization

Modularization is an essential concept in writing clean code. It refers to the practice of breaking down large, complex into smaller, more manageable modules or functions. This makes the code easier to understand, test, and maintain.

Using modularization provides several benefits such as:

  1. Re-usability: Modules can be reused in different parts of the application or in other applications, saving time and effort in development.

  2. Encapsulation: Modules allow you to hide the internal details of a function or object, exposing only the essential interface to the outside world. This helps to reduce coupling between different parts of the code and improve overall code quality.

  3. Scalability: By breaking down large code into smaller, modular pieces, you can easily add or remove functionality without affecting the entire codebase.

Here is an example in Javascript of a piece of code that performs a simple task, one is not using modularization and the other is implementing modularization.

// Without modularization
function calculatePrice(quantity, price, tax) {
  let subtotal = quantity * price;
  let total = subtotal + (subtotal * tax);
  return total;
}

// Without modularization
let quantity = parseInt(prompt("Enter quantity: "));
let price = parseFloat(prompt("Enter price: "));
let tax = parseFloat(prompt("Enter tax rate: "));

let total = calculatePrice(quantity, price, tax);
console.log("Total: $" + total.toFixed(2));

In the above example, the calculatePrice is used to calculate the total price of an item given its quality, price, and tax rate. However, this function is not modularized and is tightly coupled with the user input and output logic. This can make it difficult to test and maintain.

Now, let's see an example of the same code using modularization:

// With modularization
function calculateSubtotal(quantity, price) {
  return quantity * price;
}

function calculateTotal(subtotal, tax) {
  return subtotal + (subtotal * tax);
}

// With modularization
let quantity = parseInt(prompt("Enter quantity: "));
let price = parseFloat(prompt("Enter price: "));
let tax = parseFloat(prompt("Enter tax rate: "));

let subtotal = calculateSubtotal(quantity, price);
let total = calculateTotal(subtotal, tax);
console.log("Total: $" + total.toFixed(2));

In the above example, the calculatePrice has been broken down into 2 smaller functions: calculateSubTotal and calculateTotal. These functions are now modularized and responsible for calculating the subtotal and total, respectively. This makes the code easier to understand, test, and maintain and also makes it more reusable in other parts of the application.

Modularization can also refer to the practice of dividing single files of code into many smaller files that are later compiled back onto a single (or fewer files). This practice has the same benefits we just talked about.

if you would like to know how to implement this in Javascript using modules, check it this article.

Always check null & undefined for Objects & Arrays

Neglecting null and undefined in the case of objects & arrays can lead to errors.

So, always check for them in your code:

const person = {
  name: "Haris",
  city: "Lahore",
};
console.log("Age", person.age); // error
console.log("Age", person.age ? person.age : 20); // correct
console.log("Age", person.age ?? 20); //correct

const oddNumbers = undefined;
console.log(oddNumbers.length); // error
console.log(oddNumbers.length ? oddNumbers.length : "Array is undefined"); // correct
console.log(oddNumbers.length ?? "Array is undefined"); // correct

Avoid DOM Manipulation

In React, it's generally advised to avoid DOM manipulation because React uses virtual DOM to manage updates efficiently. Directly manipulating the DOM can lead to unexpected behavior and can interfere with React's rendering optimizations.

Bad approach: Manipulating DOM directly

import React from 'react';

function InputComponent() {
  const handleButtonClick = () => {
    const inputElement = document.querySelector('input[type="text"]');
    if (inputElement) {
      inputElement.style.border = '2px solid green';
      inputElement.focus();
    }
  };

  return (
    <div>
      <input type="text" />
      <button onClick={handleButtonClick}>Focus and Highlight Input</button>
    </div>
  );
}

export default InputComponent;

Good approach: Using useRef

import React, { useRef } from 'react';

function InputComponent() {
  const inputRef = useRef(null);

  const handleButtonClick = () => {
    inputRef.current.style.border = '2px solid green';
    inputRef.current.focus();
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleButtonClick}>Focus and Highlight Input</button>
    </div>
  );
}

export default InputComponent;

Avoid Inline Styling

Inline styling makes your JSX code messy. It is good to use classes & ids for styling in a separate .css file.

const text = <div style={{ fontWeight: "bold" }}>Happy Learing!</div>; // bad approach

const text = <div className="learning-text">Happy Learing!</div>; // good approach

In .css file:

.learning-text {
  font-weight: bold;
}

Always Remove Every Event Listener in useEffect

It's important to remove event listeners from the useEffect cleanup function to prevent memory leaks and avoid unexpected behavior in your React components:

  • Memory leaks: if you don't remove event listeners when the component unmounts, those event listeners remain in memory, even after the component is removed from the DOM. This can lead to memory leaks over time, as unused even listeners accumulate and costume memory unnecessarily.

  • Unexpected behavior: Even listeners attached to elements can cause unexpected behavior if they continue to exist after the components are unmounted

import React, { useEffect, useState } from 'react';

function ExampleComponent() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    const handleClickOutside = () => {
      setCount(count + 1); // Increment count when clicked outside
    };

    document.addEventListener('click', handleClickOutside);

    return () => {
      document.removeEventListener('click', handleClickOutside);
    };
  }, [count]); // Re-run effect when count changes

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment Count</button>
    </div>
  );
}

export default ExampleComponent;

Don't throw your files Randomly

Keep the related files in the same folder instead of making files in a single folder. When files are organized logically in the same folder, it's easy to maintain and update them. Developers know where to find the related code and make changes, reducing the risk of introducing bugs or inconsistencies.

For example, if you want to create a navbar in React then you should create a folder and place .js & .css & .test.js files related to the navbar in it.

Create a habit of Writing helper functions

Creating a habit of writing helper functions in ReactJS offers several advantages:

  1. Code Reusability: Helper functions encapsulate common logic or operations, allowing you to reuse them across different components or modules. This reduces code duplication and promotes a more modular and maintainable codebase.

  2. Improved Readability: By breaking down complex logic into smaller, more manageable helper functions, your code becomes easier to read and understand. Well-named helper functions serve as self-documenting code, conveying their purpose and functionality at a glance.

  3. Simplifying Component Logic: Writing helper functions enables you to offload non-UI-related logic from your components. This keeps your components focused on rendering UI elements and handling user interactions, leading to cleaner and more concise component code.

  4. Facilitating Testing: Helper functions can be tested independently, which makes it easier to write unit tests for your application logic. This promotes code reliability and helps catch bugs early in the development process.

  5. Encouraging Code Organization: By abstracting common operations into helper functions, you can better organize your codebase and adhere to principles of separation of concerns. This makes it easier to maintain and scale your application over time.

Overall, incorporating helper functions into your ReactJS projects promotes code reuse, readability, maintainability, testability, and code organization, ultimately leading to more efficient and robust applications.

Use ternary operator instead of if/else if statements

Using if else if statements make your code bulky. Instead, try to use a ternary operator where possible to make code simpler and cleaner.

// Bad approach
if (name === "Ali") {
  return 1;
} else if (name === "Bilal") {
  return 2;
} else {
  return 3;
}

// Good approach
name === "Ali" ? 1 : name === "Bilal" ? 2 : 3;

Make index.js File Name to minimize importing complexity

if you have a file named index.js in a directory named actions and you want to import action from it in your component, your import would be like this:

import { actionName } from "src/redux/actions";

actions the directory path is explained in the above import. Here you don't need to mention index.js after actions like this:

import { actionName } from "src/redux/actions/index";

Using Import Aliases

Import aliases simplify import statements, making them more readable and manageable, especially in large projects. Here’s how to use them effectively in Node.js, React, and Next.js 14.

Good Practice: Using Import Aliases

// In a React/Next.js project
import Button from '@components/Button';

// In a Node.js project
const dbConfig = require('@config/db');

Bad Practice: Without Import Aliases

// Complex and lengthy relative paths
import Button from '../../../components/Button';
const dbConfig = require('../../config/db');

Setting up Aliases

  • React/Next.js: Configure jsconfig.json or tsconfig.json for alias paths.

  • Node.js: Use module-alias package or configure package.json for custom paths.

Import aliases streamline project structure by reducing the complexity of import statements, and enhancing code readability and maintainability.

Effective Color Management

Proper color management is essential in web development for maintaining a consistent and scalable design. This document outlines best practices for managing colors using Tailwind CSS, CSS variables, and JSX. It also highlights common pitfalls to avoid.

Bad practices to avoid: Inline color definitions

/* Bad Practice in css */
.some-class {
  color: #333333; /* Direct color definition */
  background-color: #ffffff; /* Hardcoded color */
}

/* Bad Practice in JSX */
const MyComponent = () => (
  <div style={{ color: '#333333', backgroundColor: '#ffffff' }}>
    Content
  </div>
);

Using CSS variables for Global Color Management

CSS variables offer a flexible and maintainable approach to managing colors globally.

// Defining CSS variables
:root {
  --primary-color: #5A67D8;
  --secondary-color: #ED64A6;
  --text-color: #333333;
  --background-color: white;
  --warning-color: #ffcc00;
}

// Using CSS variables in stylesheet
.header {
  background-color: var(--primary-color);
  color: var(--background-color);
}

// Dark Mode example with CSS variables 
.dark {
  --primary-color: #9f7aea;
  --background-color: #2d3748;
  --text-color: #cbd5e0;
}

Tailwind CSS for consistent color usage

Tailwind CSS provides a utility-first approach, allowing you to define a color palette in your configuration and set it through your project.

//tailwind.config.js 
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#5A67D8',
        secondary: '#ED64A6',
        // other colors...
      },
    },
  },
  // other configurations...
};

// Using Tailwind classes in JSX
const MyComponent = () => (
  <h1 className="text-primary bg-secondary">Welcome to My Site</h1>
);

Efficient Code Structure In React Components

Organizing code within React components in a logical or efficient manner is crucial for readability and maintainability. This guide outlines the recommended order and structure for various elements within a React component.

Recommended structure inside React components

  1. Variables and Constants: Declare any constants or variables at the beginning of the component.

     const LIMIT = 10;
    
  2. State Management & other hooks (Redux, Context): Initialize Redux hooks or Context API hooks next.

     const user = useSelector(state => state.user);
    
  3. Local State (useState, useReducer): Define local state hooks after state management hooks.

     const [count, setCount] = useState(0);
    
  4. Effects (useEffect): Place useEffect hooks after state declarations to capture component lifecycle events.

     useEffect(() => {
       loadData();
     }, [dependency]);
    
  5. Event Handlers and Functions: Define event handlers and other functions after hooks.

     const handleIncrement = () => {
       setCount(prevCount => prevCount + 1);
     };
    

Example of good structure ✅

import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import SomeService from './SomeService';
import './Page.css';

const Page = ({ variant, ...props }) => {
    // Constants
    const MAX_COUNT = 10;

    // Redux State
    const user = useSelector(state => state.user);

    // Local State
    const [count, setCount] = useState(0);
    const [data, setData] = useState(null);

    // useEffect for loading data
    useEffect(() => {
        SomeService.getData().then(data => setData(data));
    }, []);

    // useEffect for user-related operations
    useEffect(() => {
        if (user) {
            console.log('User updated:', user);
        }
    }, [user]);

    // Event Handlers
    const handleIncrement = () => {
        if (count < MAX_COUNT) {
            setCount(prevCount => prevCount + 1);
        }
    };

    return (
        <div className={`page page-${variant}`}>
            <h1>Welcome, {user.name}</h1>
            <button onClick={handleIncrement}>Increment</button>
            <p>Count: {count}</p>
            {data && <div>Data loaded!</div>}
        </div>
    );
};

export default Page;

Best Practices in Code Documentation

Effective documentation is key to making code readable and maintainable. This guide covers the usage of JSDoc and the dos and don'ts of commenting.

JSDoc for Javascript

JSDoc is a popular tool for documenting Javascript code. It helps in understanding the purpose of functions, parameters, and return types.

Good JSDoc Example

/**
 * Adds two numbers together.
 * @param {number} a - The first number.
 * @param {number} b - The second number.
 * @returns {number} The sum of the two numbers.
 */
function sum(a, b) {
  return a + b;
}

Bad JSDoc Example

// Adds a and b
function sum(a, b) {
  return a + b;
}
// Missing detailed JSDoc comment

Including meaningful comments & avoid redundancy

Strategic comments enhance code clarity but beware of redundancy. Prioritize meaningful insights to facilitate collaboration and understanding among developers.

Good practice

// Loop through users and apply discounts to eligible ones
users.forEach(user => {
  if (user.isEligibleForDiscount()) {
    applyDiscount(user);
  }
});

// --------------------------------------------

// Calculate the area of a rectangle
function calculateArea(length, width) {
  return length * width;
}

Bad Practice

// Start a loop
users.forEach(user => {
  // Check if the user is eligible for discount
  if (user.isEligibleForDiscount()) {
    // Apply discount to the user
    applyDiscount(user);
  }
});
// Redundant comments that simply restate the code

// ----------------

// Calculate area
function calculateArea(l, w) {
  return l * w;
  // Ambiguous and unhelpful comment
}

Secure coding Practices

Security is a paramount aspect of web development. Writing secure code is crucial to protect against vulnerabilities like SQL injection, XSS (Cross-Site Scripting), and CSRF (Cross-Site Request Forgery).

  • Protecting Against XSS Attacks

    Cross-site scripting (XSS) attacks occur when malicious scripts are injected into web pages viewed by other users. This can lead to data theft, session hijacking, and other security breaches. To learn more about XSS

    Vulnerable Code Example:

      // Rendering user input directly to the DOM
      document.getElementById("user-content").innerHTML = userInput;
    

    Secure Code Example

      // Escaping user input before rendering
      const safeInput = escapeHtml(userInput);
      document.getElementById("user-content").textContent = safeInput;
    
      // Example: Using DOMPurify to sanitize user input
      const cleanInput = DOMPurify.sanitize(userInput);
      document.getElementById("user-content").innerHTML = cleanInput;
    
  • Mitigating CSRF Attacks

    CSRF attacks force a logged-on victim to submit a request to a web application on which they are currently authenticated. These attacks can be used to perform actions on behalf of the user without their consent. To learn more about CSRF.

    Vulnerable Code Example

      <!-- GET request for sensitive action -->
      <a href="/delete-account">Delete Account</a>
    

    Secure Code Example

      // Backend: Generate and validate CSRF tokens
      app.use(csrfProtection);
      app.post("/delete-account", (req, res) => {
        // Validate CSRF token
      });
    
      <!-- Frontend: Include CSRF token in form -->
      <form action="/delete-account" method="POST">
        <input type="hidden" name="_csrf" value="{csrfToken}" />
        <button type="submit">Delete Account</button>
      </form>
    
  • Usingnpm-auditto Identify Vulnerabilities

    Run npm audit to identify insecure dependencies. Regularly update your package to the latest, non-vulnerable versions.

  • Incorporating Synk for Continuous Security

    Integrate Snyk into your development workflow for continuous monitoring and fixing of vulnerabilities in dependencies.

  • Managing Environment Variables Securely

    Store sensitive information like API keys and passwords in .env files and access them via process.env in your code.

      // Bad Practice: Hardcoded secret
      const API_KEY = "hardcoded-secret-key";
    
      // Good Practice: Example of accessing a secret from .env file
      require("dotenv").config();
      const API_KEY = process.env.API_KEY;
    

Follow the SOLID Principle

“Single Responsibility”, “Open/Closed”, “Liskov Substitution”, “Interface Segregation”, and “Dependency Inversion” - these five principles (SOLID for short) are the cornerstones of writing code that scales and is easy to maintain.

Utilize Design Patterns, but don't over-design

Design patterns can help us show some common problems. However, every pattern has its applicable scenarios. Overusing or misusing design patterns may make your code more complex and difficult to understand.

Below are these design patterns you should know:

  • Factory

  • Behavioral

  • Strategy

  • Proxy

  • Structural

  • Adapter

  • Singleton

  • Creational

Wrapping up

I hope you enjoyed the article and learned something new :)))

Happy coding!!!!!!

References

https://dev.to/sathishskdev/part-4-writing-clean-and-efficient-react-code-best-practices-and-optimization-techniques-423d

https://dev.to/perssondennis/react-anti-patterns-and-best-practices-dos-and-donts-3c2g

https://najm-eddine-zaga.medium.com/18-best-practices-for-react-617e23ed7f2c

https://www.freecodecamp.org/news/best-practices-for-react/

https://www.freecodecamp.org/news/how-to-write-clean-code/#clear-flow-of-execution

https://dev.to/iambilalriaz/react-best-practices-ege?ref=dailydev

https://peacockindia.mintlify.app/introduction