Skip to content
This repository was archived by the owner on Feb 9, 2025. It is now read-only.
This repository was archived by the owner on Feb 9, 2025. It is now read-only.

null vs undefined #71

@jhnns

Description

@jhnns

I would like to start a discussion about null and undefined. In the end I would like to use only one value to represent a non-value.

Motivation

In my experience it's problematic when developers start to use both to represent the absence of a value. When a value can either be null or undefined or something else, people start to write code like this:

if (someValue) {

This is problematic because it may introduce bugs where someValue is casted implicitly to false (e.g. with 0 or ""). The alternative would be to use:

if (someValue == null) {

which conflicts with our rule to use strict equality checks. It's also not intuitive that == null will also check for undefined. The biggest problem to me is that == null is also not optimizable for the browser because document.all == null might also evaluate to true (this is a little known fact).

The last alternative would be to write:

if (someValue === undefined || someValue === null)

which is just annoying.

But there are more problems. JS default parameters for functions and destructuring will only work with undefined. This makes it even more complicated if the codebase is mixing undefined and null. On the other hand, JSON.stringify will just omit undefined values.

A codebase cannot ignore these issues in my experience. It should stick to a guideline.

Comparison

Pro undefined

  • Function default parameters only work with undefined
  • Destructuring with default values only work with undefined
  • typeof "object" works as intended
  • Arithmetic with non-existent values produces expected results:
    • 2 + null; // 2
    • 2 + undefined; // NaN

Pro null

  • JSONs can only contain null
  • React components can only return null for rendering nothing (undefined is not supported)
  • DOM APIs often use null
  • Functions can distinguish between implicit return and return null (although it's not a good style to mix both in the same function)
  • Distinguishing between a non-existent object property and a null property is straightforward:
    • obj.prop === undefined means it doesn't exist
    • obj.prop === null means it exists but it's not set

My conclusion

For a long time (especially pre-ES2015) I was pro null because setting something to null explicitly was a clear sign that "there is something, it's just not set". It maps clearly to the concept of known knowns, known unknowns (null) and unknown unknowns (undefined).

However, with the rise of ES2015 and the widespread use of default parameters and values, the usability problems became more apparent. In one of our projects we had to do something like this just to use default props:

<SomeComponent
   a={model.a === null ? undefined : model.a}
   b={model.b === null ? undefined : model.b}
   c={model.c === null ? undefined : model.c}
/>

Since this is too much code to write and read, people will either deviate from the rule to use null and use undefined where it's easier for them (eventually leading to the situation I mentioned at the beginning) or use unsafe boolean expressions like:

<SomeComponent
   a={model.a || undefined}
   b={model.b || undefined}
   c={model.c || undefined}
/>

Nullish Coalescing will make that less error prone, but it's still unnecessary code in my opinion.

So my proposal would be to ban null and only use undefined. The eslint-plugin-no-null rule could enforce that.

For situations where developers need to use null (e.g. when returning nothing from React components) we can still have a single module that exports a null value:

// eslint-disable-next-line no-null/no-null
const NULL = null;
export default NULL;

For situations where developers need to save undefined to a JSON it's possible to have a utility function that converts all explicit undefined properties into null before sending it to JSON.stringify(). For consistency reasons you would also need the opposite for JSON.parse(). The null module and these helper functions could be published to NPM so that you don't have to write or copy that again.

I would like to add the no-null/no-null rule as a style rule to our ESLint config. This means that we can try it out in a project and make some experience with it.


Side notes

Defining optional properties with undefined is easier in TypeScript:

type A = {
   a?: number;
};

vs

type A = {
   a: number | null;
};

What do you think?

Other opinions:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions