DEV Community

Sam Abaasi
Sam Abaasi

Posted on

Avoiding Unsafe Calls in JavaScript and React Projects with ESLint

Avoiding Unsafe Calls in JavaScript and React Projects with ESLint

πŸ“œβœ¨ In modern JavaScript and React applications, it's common to encounter runtime errors caused by accessing properties on undefined or null values, or calling methods on undefined arrays or objects. These issues can disrupt user experience and make debugging a nightmare. In this article, we'll identify the common issues and provide an ESLint configuration to mitigate them effectively. πŸŒŸπŸ’»

🌟✨ In our React or React Native projects, because we are not using TypeScript, we sometimes forget to write safe code. These unsafe codes can lead to many issues in production, like crashing React Native apps, frustrating users, and complicating maintenance. Let's dive into these common issues and how to solve them. πŸš¨πŸš€πŸ’”

Common Issues with Unsafe Calls

1. Accessing Properties on undefined or null πŸŒŸπŸ”βœ¨

Problem:

Accessing a property on an object that is undefined or null causes a runtime error:

const user = undefined;
console.log(user.name); // ❌ Runtime Error: Cannot read property 'name' of undefined
Enter fullscreen mode Exit fullscreen mode

Solution:

  • Solution 1: Use optional chaining (?.).
const name = user?.name;
Enter fullscreen mode Exit fullscreen mode
  • Solution 2: Employ a default fallback.
const name = user ? user.name : 'Default Name';
Enter fullscreen mode Exit fullscreen mode
  • Solution 3: Ensure initialization before access.
const user = { name: 'Default Name' };
Enter fullscreen mode Exit fullscreen mode

2. Calling Methods on undefined or null βœ‹πŸ“‰πŸ“‹

Problem:

Calling methods like .map() or .filter() on an undefined array throws an error:

const items = undefined;
items.map((item) => console.log(item)); // ❌ Runtime Error: Cannot read property 'map' of undefined
Enter fullscreen mode Exit fullscreen mode

Solution:

  • Solution 1: Verify that the variable is an array.
if (Array.isArray(items)) {
  items.map(item => console.log(item));
}
Enter fullscreen mode Exit fullscreen mode
  • Solution 2: Provide a default array.
const items = someValue || [];
items.map(item => console.log(item));
Enter fullscreen mode Exit fullscreen mode
  • Solution 3: Use the nullish coalescing operator (??).
const items = possibleItems ?? [];
Enter fullscreen mode Exit fullscreen mode

3. Invoking Undefined Functions βš™οΈβš οΈπŸš«

Problem:

Trying to call a function that might be undefined:

const handler = undefined;
handler(); // ❌ Runtime Error: handler is not a function
Enter fullscreen mode Exit fullscreen mode

Solution:

  • Solution 1: Check existence before invocation.
if (typeof handler === 'function') {
  handler();
}
Enter fullscreen mode Exit fullscreen mode
  • Solution 2: Assign a no-op default function.
const handler = passedHandler || (() => {});
Enter fullscreen mode Exit fullscreen mode

4. Destructuring Undefined or Null Objects πŸ“¦βŒπŸ”“

Problem:

Destructuring properties from an undefined object results in an error:

const obj = undefined;
const { name } = obj; // ❌ Runtime Error: Cannot destructure property 'name' of undefined
Enter fullscreen mode Exit fullscreen mode

Solution:

  • Solution 1: Use optional chaining with defaults.
const { name = 'Default Name' } = user || {};
Enter fullscreen mode Exit fullscreen mode
  • Solution 2: Validate before destructuring.
if (user) {
  const { name } = user;
}
Enter fullscreen mode Exit fullscreen mode

5. Accessing Non-Existent Array Elements πŸš«πŸ“‹πŸ“Š

Problem:

Accessing elements of an undefined array causes an error:

const arr = undefined;
const first = arr[0]; // ❌ Runtime Error: Cannot read property '0' of undefined
Enter fullscreen mode Exit fullscreen mode

Solution:

  • Solution 1: Provide a default fallback.
const first = (arr && arr[0]) || 'Default Value';
Enter fullscreen mode Exit fullscreen mode
  • Solution 2: Initialize arrays properly.
const arr = arr || [];
const first = arr[0];
Enter fullscreen mode Exit fullscreen mode

6. Invalid Array/Function Usage πŸ›‘πŸ“

Problem:

Using array methods like .map() or .filter() on undefined values or objects:

const numbers = undefined;
numbers.filter((n) => n > 0); // ❌ Runtime Error: Cannot read property 'filter' of undefined
Enter fullscreen mode Exit fullscreen mode

Solution:

  • Solution 1: Always validate inputs for array functions.
if (Array.isArray(numbers)) {
  numbers.filter(n => n > 0);
}
Enter fullscreen mode Exit fullscreen mode
  • Solution 2: Return a safe result if input isn't valid.
const results = Array.isArray(numbers) ? numbers.filter(n => n > 0) : [];
Enter fullscreen mode Exit fullscreen mode

7. Insufficient Conditional Checks πŸ’‘πŸ”’

Problem:

Failing to validate conditions strictly can lead to bugs, such as relying on falsy values. For example, if conditions expecting a boolean might incorrectly evaluate other types like undefined or 0:

const obj = {};
if (obj.prop) {
  // Executes even if obj.prop is `undefined`, leading to unexpected behavior
}
Enter fullscreen mode Exit fullscreen mode

Solution:

  • Solution 1: Use strict equality comparisons.
if (obj.prop === true) {
  console.log('Do something');
}
Enter fullscreen mode Exit fullscreen mode
  • Solution 2: Coerce values explicitly for intended behavior.
if (!!obj.prop) {
  console.log('Do something');
}
Enter fullscreen mode Exit fullscreen mode
  • Solution 3: Define explicit conditions in your code.
if (typeof obj.prop === 'boolean' && obj.prop) {
  console.log('Safe conditional execution');
}
Enter fullscreen mode Exit fullscreen mode

Using ESLint to Avoid Unsafe Calls πŸš€πŸ”’βœ…

To catch these issues during development, we can leverage ESLint with specific rules. Below is an ESLint configuration that will flag unsafe calls and suggest fixes. πŸ› οΈπŸ”πŸŒŸ

ESLint Configuration πŸ“œπŸ’»πŸ›‘οΈ

Add the following rules to your .eslintrc.js or ESLint configuration file:

module.exports = {
  parser: '@typescript-eslint/parser', // If using TypeScript
  plugins: ['@typescript-eslint'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
  ],
  parserOptions: {
    ecmaVersion: 2020, // or later
    sourceType: 'module',
    project: './tsconfig.json', // Type-aware linting
  },
  rules: {
    // 1. Disallow accessing properties on undefined/null
    '@typescript-eslint/no-unnecessary-condition': 'warn',

    // 2. Enforce optional chaining where needed
    'no-unused-expressions': ['warn', { allowShortCircuit: true }],

    // 3. Disallow unsafe calls (functions on undefined/null)
    '@typescript-eslint/no-unsafe-call': 'error',

    // 4. Disallow unsafe member access (accessing props on undefined/null)
    '@typescript-eslint/no-unsafe-member-access': 'error',

    // 5. Catch invalid destructuring of undefined or null
    '@typescript-eslint/no-unnecessary-condition': 'warn',

    // 6. Catch invalid array/function usage (e.g., map/filter on undefined)
    'consistent-return': 'warn',

    // 7. Enforce stricter conditional checks
    '@typescript-eslint/strict-boolean-expressions': [
      'error',
      {
        allowNullableObject: false,
        allowNullableBoolean: true,
        allowNullableString: true,
        allowNumber: true,
        allowAny: false,
      },
    ],

    // Additional rules for clarity and safety:
    'no-implicit-coercion': ['warn', { allow: ['!!'] }],
    'no-unreachable': 'error',
    '@typescript-eslint/no-non-null-assertion': 'error',
  },
};
Enter fullscreen mode Exit fullscreen mode

Explanation of Rules πŸ’‘βš™οΈπŸŒ

  1. @typescript-eslint/no-unnecessary-condition: Flags unnecessary conditions or unhandled potential undefined or null values. ⚠️
  2. no-unused-expressions: Ensures that short-circuited logic like someObject && someObject.doSomething is avoided unless explicitly necessary. πŸš€
  3. @typescript-eslint/no-unsafe-call: Prevents unsafe function calls on non-functions. ❌
  4. @typescript-eslint/no-unsafe-member-access: Flags attempts to access properties on potentially undefined values. βœ‹
  5. consistent-return: Enforces consistent return types in functions to avoid returning invalid or undefined values. πŸ’Ύ
  6. @typescript-eslint/strict-boolean-expressions: Strengthens conditional expressions by preventing implicit coercion. πŸ”’
  7. @typescript-eslint/no-non-null-assertion: Disallows the unsafe ! operator used to bypass null/undefined checks. 🚫

Installing Required Dependencies πŸ“¦πŸ”§πŸ”

To enable these rules, ensure you have the necessary ESLint plugins and parsers installed:

npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
Enter fullscreen mode Exit fullscreen mode

Integrating ESLint with VSCode πŸ–₯️🚦🌈

1. Install the ESLint Extension:

  • Search for "ESLint" in the VSCode marketplace and install it. ✨

2. Enable Auto-Fixing:

Add the following to your settings.json:

{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"]
}
Enter fullscreen mode Exit fullscreen mode

3. Run ESLint:

Add an npm script to run ESLint:

"scripts": {
  "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
}
Enter fullscreen mode Exit fullscreen mode

Then, run npm run lint to catch issues. πŸš€


Conclusion πŸŒŸπŸš€πŸ“˜

By implementing the above ESLint rules and practices, you can catch and fix unsafe calls before they become runtime errors. πŸŽ‰πŸŒˆ This approach will improve the reliability and maintainability of your JavaScript and React projects. πŸŒβš™οΈβœ¨


Image of Datadog

How to Diagram Your Cloud Architecture

Cloud architecture diagrams provide critical visibility into the resources in your environment and how they’re connected. In our latest eBook, AWS Solution Architects Jason Mimick and James Wenzel walk through best practices on how to build effective and professional diagrams.

Download the Free eBook

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

Retry later