Convert to optional chaining

Replace various guard expressions with the optional chaining operator (?.).

Refactoring (Convert)
Modernization
ES2020
P42 for VS Code
P42 for GitHub
Convert to optional chaining

The optional chaining operator .? returns the value of an object property when the object is available and undefined otherwise. It is similar to the standard . chaining operator, with an added check if the object is defined (i.e., not nullish).

The optional chaining operator lets you write concise and safe chains of connected objects when some of those objects can be null or undefined. Before the introduction of optional chaining in ES2020, the && operator was often used to check if an object is available (obj && obj.value).

You can often simplify existing checks with the optional chaining pattern:

  • obj && obj.property becomes obj?.property
  • obj != null && obj.property becomes obj?.property
  • obj != null ? obj.property : undefined becomes obj?.property
  • arr && arr[i] becomes arr?.[i]
  • f && f() becomes f?.()
  • etc.

What do I need to consider?

Optional chaining short-circuits for nullish values, but not for other falsy values

When a && a.b is replaced with a?.b, the execution for types that can have falsy values is changed. This means that the result value and type of the expression can be different with optional chaining.

The following snippet shows some examples:

function test(value) {
    console.log(`${value && value.length}, ${value?.length}`);
}

test(undefined);       // undefined, undefined
test(null);            // null, undefined
test(true);            // undefined, undefined
test(false);           // false, undefined
test(1);               // undefined, undefined
test(0);               // 0, undefined
test({});              // undefined, undefined
test([]);              // 0, 0
test({ length: "a" }); // a, a
test('');              // , 0
test(NaN);             // NaN, undefined

The empty string, which is falsy, but not nullish, can be especially problematic. Here is an example were introducing optional chaining can lead to problems:

// without optional chaining
if (s && s.length === 0) {
  // not called for the empty string 
  // (e.g., legacy code that works this way)
}

// with optional chaining
if (s?.length === 0) {
  // called for the empty string 
  // (potentially introducing undesired behavior)
}

Optional chaining changes the result for null to undefined

When calling a?.b with null, the result is undefined. However, with a && a.b, the result is null.

Optional chaining can affect the number of calls with side effects

For example, consider refactoring

f() && f().a;

into

f()?.a;

With &&, f is called one or two times. However, with optional chaining f is only called once. If f has a side effect, this side effect would have been called a different number of times, potentially changing the behavior. This behavior applies not just to function and methods calls but also to getters that can potentially have side effects.

TypeScript does not support optional chaining of the 'void' type

TypeScript does not support optional chaining for void, event though the corresponding JavaScript code would work.

type Input = void | {
    property: string
};

function f(input: Input) {
    // this works:
    console.log(input && input.property);
    // this breaks because void is not undefined in TypeScript:
    console.log(input?.property);
}

Old browsers and JavaScript engines do not support optional chaining

Optional chaining is an ES2020 feature. It is supported on all modern browsers and Node 14+, but for older browsers and Node versions, transpilation might be required (compatibility).

Learn More

Available In