Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

null vs undefined #71

Open
jhnns opened this issue Aug 23, 2019 · 2 comments
Open

null vs undefined #71

jhnns opened this issue Aug 23, 2019 · 2 comments

Comments

@jhnns
Copy link
Member

jhnns commented Aug 23, 2019

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:

@jhnns
Copy link
Member Author

jhnns commented Dec 26, 2019

Sindre Sorhus is thinking about the same: sindresorhus/meta#7

@jhnns
Copy link
Member Author

jhnns commented Dec 26, 2019

Just for the record:

undefined wasn't a reserved word in pre-ES5 which is why it used to be best practice to use typeof something === "undefined" or something === void 0. In ES5 environments and later, however, undefined is a read-only value that cannot be changed. This is why these comparisons are safe nowadays.

jhnns added a commit that referenced this issue Feb 5, 2020
Forbids the usage of `null`. In a codebase it's often better to use a single non-value to represent *the absence of a value*. With the rise of default parameters and destructuring defaults, JavaScript developed a clear tendency towards `undefined`. [This issue](#71) summarizes the arguments (and trade-offs) of **null vs. undefined**.

**Please note:** If you use this rule, you will probably still need a single `null` value which you can refer to whenever you need to use `null` because of third-party code:

```js
// eslint-disable-next-line no-null/no-null
export const NULL = null;
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant