10 things you should (almost) never do in Javascript and ReactJS (with code examples)

emiquelito

Evandro Miquelito

Posted on April 21, 2023

10 things you should (almost) never do in Javascript and ReactJS (with code examples)

Javascript - things to avoid

As a general guideline, here are ten things you should avoid doing with JavaScript to write maintainable and efficient code:

1. Avoid using global variables

Global variables can lead to naming conflicts, make debugging difficult, and result in unintended consequences. Use local variables and follow proper scoping techniques.

Example:

// Avoid using global variables

// Outdated: Using var for global variable declaration
var globalVar = 'I am a global variable';

function foo() {
  console.log(globalVar);
}

foo(); // Outputs: I am a global variable

// Updated: Using let for local variable declaration
let localVar = 'I am a local variable'; 

function bar() {
  let localVar = 'I am a local variable inside a function'; // Shadows the globalVar
  console.log(localVar);
}

bar(); // Outputs: I am a local variable inside a function
console.log(localVar); // Outputs: I am a local variable

// Updated: Using const for constant variable declaration
const constVar = 'I am a constant variable'; 

// Trying to reassign a constant variable will result in an error
// constVar = 'Trying to reassign a constant variable'; // Throws an error

Enter fullscreen mode Exit fullscreen mode

In this example, we avoid using global variables and instead use local variables within the appropriate function scope. The globalVar is a global variable, which can be accessed from any part of the code, but it can also lead to naming conflicts and unintended consequences. On the other hand, localVar and innerVar are local variables, which are scoped within the demonstrateLocalVariables function and the innerFunction respectively. This helps prevent naming conflicts, makes debugging easier, and results in more predictable code behavior.

2. Avoid using eval()

The use of eval() can introduce security risks and potential vulnerabilities, as it executes arbitrary code. Use alternative approaches whenever possible, such as using JSON.parse() for parsing JSON data.

3. Avoid blocking the main thread

JavaScript runs in a single-threaded environment, and blocking the main thread with long-running tasks can cause unresponsive user interfaces. Use asynchronous programming techniques, such as Promises or async/await, to avoid blocking the main thread.

Example:

// Function that simulates a long-running task
function doSomeLongRunningTask() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Long-running task complete');
      resolve();
    }, 3000); // Simulating a 3-second task
  });
}

// Function that uses Promises to avoid blocking the main thread
function runAsyncTask() {
  console.log('Starting asynchronous task');
  return doSomeLongRunningTask()
    .then(() => {
      console.log('Asynchronous task complete');
    })
    .catch((error) => {
      console.error('Error in asynchronous task:', error);
    });
}

console.log('Before running asynchronous task');
runAsyncTask();
console.log('After running asynchronous task');

// Output:
// Before running asynchronous task
// Starting asynchronous task
// After running asynchronous task
// Long-running task complete
// Asynchronous task complete
Enter fullscreen mode Exit fullscreen mode

In this example, doSomeLongRunningTask() simulates a long-running task that takes 3 seconds to complete. Instead of blocking the main thread and causing an unresponsive user interface, we wrap the task in a Promise and use .then() and .catch() to handle the asynchronous result. This allows the main thread to continue running, and the user interface remains responsive during the execution of the long-running task.

4. Avoid excessive DOM manipulation

Manipulating the DOM (Document Object Model) can be expensive in terms of performance. Minimize unnecessary DOM operations, batch DOM updates, and use event delegation when possible.

Example:

// Bad practice: Excessive DOM manipulation
function updateElementInnerTextBad() {
  for (let i = 0; i < 1000; i++) {
    const element = document.createElement('p');
    element.innerText = `Element ${i}`;
    document.body.appendChild(element);
  }
}

// Good practice: Minimize DOM manipulation
function updateElementInnerTextGood() {
  const container = document.createDocumentFragment();
  for (let i = 0; i < 1000; i++) {
    const element = document.createElement('p');
    element.innerText = `Element ${i}`;
    container.appendChild(element);
  }
  document.body.appendChild(container);
}

// Call updateElementInnerTextBad
console.time('Bad DOM manipulation');
updateElementInnerTextBad();
console.timeEnd('Bad DOM manipulation');

// Call updateElementInnerTextGood
console.time('Good DOM manipulation');
updateElementInnerTextGood();
console.timeEnd('Good DOM manipulation');
Enter fullscreen mode Exit fullscreen mode

In this example, we have two functions updateElementInnerTextBad and updateElementInnerTextGood that create and append 1000 <p> elements to the DOM. However, updateElementInnerTextBad manipulates the DOM directly within a loop, which can be inefficient and result in performance issues. On the other hand, updateElementInnerTextGood minimizes DOM manipulation by creating a document fragment, appending the elements to the fragment, and then appending the entire fragment to the DOM in a single operation. This approach is more efficient and can lead to better performance, especially when dealing with a large number of DOM elements.

5. Avoid ignoring error handling

Neglecting error handling can lead to unexpected behavior and difficult debugging. Always handle errors appropriately and use try-catch blocks or error callbacks to gracefully handle errors in your JavaScript code.

Example:

// Bad practice: Ignoring error handling
function badErrorHandling() {
  try {
    // Code that may throw an error
    const result = someFunctionThatMayThrowError();
    console.log(`Result: ${result}`);
  } catch (error) {
    // Ignoring the error
  }
}

// Good practice: Proper error handling
function goodErrorHandling() {
  try {
    // Code that may throw an error
    const result = someFunctionThatMayThrowError();
    console.log(`Result: ${result}`);
  } catch (error) {
    // Handle the error appropriately
    console.error(`Error: ${error.message}`);
  }
}

// Call badErrorHandling
console.log('Bad Error Handling:');
badErrorHandling(); // Error may be silently ignored

// Call goodErrorHandling
console.log('Good Error Handling:');
goodErrorHandling(); // Error will be properly handled
Enter fullscreen mode Exit fullscreen mode

In this example, we have two functions badErrorHandling and goodErrorHandling that use a try-catch block to handle potential errors thrown by a function someFunctionThatMayThrowError(). However, badErrorHandling ignores the error by not doing anything with it, which can result in silent failures and make it difficult to debug issues. On the other hand, goodErrorHandling properly handles the error by logging the error message to the console using console.error() method. This allows for better error identification and resolution, making the code more robust and reliable.

6. Avoid using synchronous XMLHttpRequest (XHR) requests

Synchronous XHR requests can block the main thread and negatively impact the user experience. Use asynchronous XMLHttpRequest or modern alternatives like Fetch API or Axios for asynchronous data retrieval.

7. Avoid using outdated or unnecessary JavaScript features

JavaScript is an evolving language, and it's important to stay up-to-date with the latest language features and best practices. Avoid using deprecated or outdated JavaScript features that may no longer be recommended or supported.

Here are 10 outdated JavaScript features that should be avoided in modern JavaScript development:

  1. var for variable declaration: var is the old way of declaring variables in JavaScript and has some quirks and limitations, such as not having block-level scoping and being prone to hoisting. It is recommended to use let or const instead for variable declaration.

  2. function keyword for defining functions: The function keyword for defining functions has some limitations, such as not supporting arrow functions, and can have issues with scoping and hoisting. It is recommended to use arrow functions or function expressions instead.

  3. == for equality comparison: The loose equality comparison with == can have unexpected results due to type coercion. It is recommended to use strict equality comparison with === to avoid type coercion issues.

  4. document.write(): This function was commonly used in the past to write content directly to the HTML document, but it is considered outdated and can cause issues with document structure, performance, and security. It is recommended to use DOM manipulation methods instead.

  5. eval(): This function executes a string of JavaScript code as code, which can be dangerous due to potential security risks, code injection vulnerabilities, and performance issues. It is generally not recommended to use eval() in modern JavaScript code.

  6. arguments object: The arguments object was commonly used in the past to access function arguments, but it has limitations and can be error-prone. It is recommended to use rest parameters or spread syntax instead.

  7. for...in loop for iterating over objects: The for...in loop can have issues with enumerable properties and prototype chain traversal. It is recommended to use for...of or Object.keys() for iterating over object properties.

  8. escape() and unescape(): These functions were used for URL encoding and decoding, but they are considered outdated and unsafe. It is recommended to use encodeURIComponent() and decodeURIComponent() instead.

  9. __proto__: This property was used to access the prototype of an object, but it is considered outdated and has been deprecated in favor of Object.getPrototypeOf() and Object.setPrototypeOf() for better performance and compatibility.

  10. with(): This statement was used to create a temporary scope for object properties, but it is considered outdated and error-prone. It can lead to unexpected behavior and is not recommended to be used in modern JavaScript code.

It's important to keep up-to-date with the latest JavaScript standards and best practices, and avoid using outdated features that can lead to issues with performance, security, and compatibility in modern JavaScript development.

8. Avoid overusing or inefficiently using loops

Loops can be resource-intensive, especially with large data sets. Be mindful of loop performance, and use techniques like map(), filter(), and reduce() for more efficient and concise array operations.

Example:

// Inefficient use of loops
const numbers = [1, 2, 3, 4, 5];
const sum = 0;

for (let i = 0; i < numbers.length; i++) {
  sum += numbers[i];
}

console.log(sum);
Enter fullscreen mode Exit fullscreen mode

In this example, we are using a for loop to calculate the sum of an array of numbers. However, this approach is inefficient because it requires accessing each element in the array using an index, and the loop needs to iterate over the entire array even if we just need to calculate the sum.

A more efficient approach would be to use the reduce() method, which allows us to calculate the sum in a single iteration, without the need for a loop:

// Efficient use of reduce method
const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce((acc, curr) => acc + curr, 0);

console.log(sum);
Enter fullscreen mode Exit fullscreen mode

In this updated example, we are using the reduce() method to calculate the sum of the numbers array. The reduce() method takes a callback function as an argument, which is called on each element of the array, and accumulates the result in the acc parameter. The second argument 0 in the reduce() method specifies the initial value of the accumulator. This approach is more efficient as it avoids unnecessary iterations and provides a more concise and readable code.

9. Avoid using blocking or CPU-intensive operations in the main thread

JavaScript is single-threaded, and CPU-intensive operations can cause the browser to become unresponsive. Offload heavy computations or time-consuming tasks to Web Workers or other background threads.

Example:

// Blocking operation in the main thread
function fibonacci(n) {
  if (n <= 1) {
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

// Blocking operation in the main thread
console.log(fibonacci(40));
Enter fullscreen mode Exit fullscreen mode

In this example, we have a function that calculates the Fibonacci sequence using recursion. However, this approach is blocking and CPU-intensive, as it performs multiple recursive calls, resulting in a long computation time. If this function is called with a large value of n, it can block the main thread and cause the browser to become unresponsive.

To avoid blocking the main thread, we can use asynchronous techniques, such as Web Workers or Promises, to offload CPU-intensive tasks to separate threads or use non-blocking algorithms. Here's an example using Web Workers:

// Using Web Workers to offload computation to a separate thread
// main.js
const worker = new Worker('fibonacciWorker.js');

worker.postMessage(40); // Send data to the worker

worker.onmessage = function(event) {
  console.log(event.data); // Receive computed result from the worker
};

// fibonacciWorker.js
function fibonacci(n) {
  if (n <= 1) {
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

self.onmessage = function(event) {
  const result = fibonacci(event.data); // Compute result
  self.postMessage(result); // Send result back to the main thread
};
Enter fullscreen mode Exit fullscreen mode

In this updated example, we use a separate Web Worker to perform the Fibonacci calculation, leaving the main thread free to handle user interactions and prevent blocking. The postMessage() method is used to send data between the main thread and the worker, and the onmessage event is used to receive the computed result. This approach ensures that the main thread remains responsive and provides a better user experience.

10. Avoid relying solely on client-side validation

Client-side validation can be bypassed, and it's not a reliable security measure. Always validate input on the server-side to ensure data integrity and security.

ReactJS - things to avoid

As React is a popular JavaScript library for building user interfaces, here are ten things to avoid doing when working with React to ensure efficient and maintainable code:

1. Avoid directly manipulating the DOM

React is designed to manage the DOM efficiently through its virtual DOM (vDOM) abstraction. Avoid manipulating the DOM directly using techniques like document.getElementById or innerHTML, and instead use React's declarative approach to manage the UI.

2. Avoid using setState in the render method

Modifying state using setState within the render method can lead to an infinite loop of renders. Keep the render method pure by only rendering UI components based on props and state.

3. Avoid using too many unnecessary re-renders

Re-renders can impact performance. Avoid unnecessary re-renders by using shouldComponentUpdate, React.memo, or PureComponent to optimize component rendering.

4. Avoid using forceUpdate

Using forceUpdate should be avoided in most cases, as it bypasses React's normal re-rendering mechanism and can lead to unpredictable behavior.

5. Avoid using refs excessively

While refs can be useful for accessing DOM elements or managing focus, overuse of refs can lead to spaghetti code and make it harder to manage state and data flow in your application. Prefer lifting state or using callback functions to manage component interactions.

6. Avoid using a large number of stateful components

Managing state can become complex in large applications with numerous stateful components. Consider using state management libraries like Redux or MobX to centralize and manage application state more effectively.

Example:

import React, { useState } from 'react';

const App = () => {
  const [count, setCount] = useState(0);

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

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

export default App;
Enter fullscreen mode Exit fullscreen mode

In this example, we have a simple App component that displays a count value and an increment button. The count value is managed using the useState hook, which allows us to manage local state in a functional component.

By using hooks like useState and useEffect, we can manage local state and side effects in functional components, eliminating the need to create a large number of stateful class components. This approach avoids potential performance issues associated with class components and simplifies the codebase, making it easier to maintain and debug.

7. Avoid using inline styles or CSS in JS excessively

While React supports inline styles and CSS-in-JS, excessive use of these techniques can make it harder to manage and maintain your styles. Consider using external CSS files or CSS modules for better separation of concerns.

8. Avoid ignoring component lifecycle methods

React has lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount that can be used to manage component lifecycle events. Ignoring these methods can result in unhandled side effects or memory leaks.

9. Avoid using anonymous functions as props

Passing anonymous functions as props can lead to unnecessary re-renders of child components. Instead, use memoization techniques like useCallback or React.memo to prevent unnecessary re-renders.

10. Avoid ignoring performance optimizations

React provides various performance optimizations like lazy loading, code splitting, and memoization. Ignoring these optimizations can result in reduced performance and slower load times for your application.

Remember, best practices and performance optimizations can vary depending on the specific requirements and complexity of your React application. It's important to stay updated with React's latest features and best practices, and to continuously review and optimize your code to ensure efficient and maintainable React applications.

Conclusion

There's no one-size-fits-all solution when it comes to best practices in JavaScript. It's all about understanding the unique context and requirements of your project - bonus point if you can understand some of the most common traits of ineffective engineering teams. To write code that's truly robust and efficient, make sure you have a deep grasp of JavaScript and its limitations, stay up-to-date with industry standards, and never stop learning. Keep honing your skills and staying curious to ensure you're always on top of your game!

💖 💪 🙅 🚩
emiquelito
Evandro Miquelito

Posted on April 21, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related