Skip to content

Instantly share code, notes, and snippets.

@slikts
Last active January 13, 2023 19:22
Show Gist options
  • Star 98 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save slikts/dee3702357765dda3d484d8888d3029e to your computer and use it in GitHub Desktop.
Save slikts/dee3702357765dda3d484d8888d3029e to your computer and use it in GitHub Desktop.
A case for using `void` in modern JavaScript

nelabs.dev

A case for using void in modern JavaScript

The void operator has historically had a few use cases:

  • Used with <a href="javascript:void(0)"> to implement dynamic buttons, but this is a poor and dated practice
  • Used to access the primitive undefined value in the bad old days prior to ES5 when it was possible to overwrite the identifier undefined
  • Used to save a few characters and shorten undefined to just void 0; this is still used by minifiers

None of these use cases are particularly relevant to modern JavaScript, except for a new case that's been introduced with arrow functions in ES6:

Using void to explicate non-value-returning (void) arrow functions

ES6 arrow function expressions allow omitting the braces from the function body to make code more terse, but it creates an ambiguity about when the return value is or isn't intended to be used, because the function might be intended just for side effects:

// omitting braces
const id = x => x;

// function called just for side effects
const log = x => console.log(x);

The Console API is widely familiar, so this particular example isn't ambiguous, but it illustrates the principle.

Intentional void arrow functions can be made explicit by adding braces, but this makes them less terse:

const log = x => {
  console.log(x);
}

The function could still be formatted in a single line, but automatic formatting tools like Prettier will usually add line breaks, and the intention to not return a value is not as clear as when using the void operator:

const log = x => void console.log(x);

The most noticeable benefit of using void like this is reducing the line count; especially in programming styles like CPS that rely on void callbacks often.

Type systems and void type

Many type systems, including TypeScript's, have the concept of a void type, which makes the use of the void operator for non-value-returning functions complementary and more recognizable.

React useEffect hook

A common practical use case for void is with the useEffect hook in React:

useEffect(() => void (document.title = 'example'));

It's a particularly salient example because the useEffect hook can use its callback's return value for cleaning up the effect, so accidentally returning a function could be a runtime bug.

Using void with async IIFES

There's an additional but less important use case for the void operator with async immediately-invoked function expressions (IIFES). Traditionally IIFES were used to implement modular scopes, since JS didn't have block scope or actual modules, but modern JS has both, so IIFES are largely not relevant except for one case: using theawait or for-await-of syntax at the top level of modules:

void (async () => {
  await fetch(something);
})();

This is a popular enough use case that there exists a stage 3 proposal for it. Parenthesis are still required around the function expression due to precedence rules, but using void solves the problem that leading parenthesis don't work with ASI (more specifically, it breaks without explicit semicolon line terminators).

Style guides and no-void

Popular style guides like Airbnb's and Standard JS use the no-void ESLint rule to prohibit using the void operator, but it's arguably an oversight and should be overridden by users.

@joakim
Copy link

joakim commented Dec 21, 2019

I agree, I don't see what's wrong with a language feature that elegantly solves a real problem. void seemed like a leftover from the old days, until arrow functions made it relevant and useful again.

Another use case is Immer's produce():

produce(draft => void (draft.user.age += 1))

https://immerjs.github.io/immer/docs/return#inline-shortcuts-using-void

@slikts
Copy link
Author

slikts commented Dec 21, 2019

@joakim Immer producers are a good example for the same reason as useEffect, because the return value of those callbacks matters, but the use of the comma operator in Immer's example is a bridge too far for me.

@avindra
Copy link

avindra commented Mar 14, 2020

In React, it can be also used to prevent any changes to a input.

This can be used to ensure text can be copied from the input via the users' selection.

<input type="text" onChange={void(0)} value="This value cannot be changed by the user" />

@slikts
Copy link
Author

slikts commented Mar 14, 2020

That's what the readonly attribute is for.

@alinnert
Copy link

alinnert commented Apr 2, 2020

For some cases it's very handy, but why would I write

useEffect(() => void (document.title = 'example'));

instead of just

useEffect(() => { document.title = 'example' });

? I think in this specific case not using void is shorter and more readable than using void. The thing is you need to wrap the statement in something either way.

@slikts
Copy link
Author

slikts commented Apr 2, 2020

ESLint with brace-style enabled or Prettier will format a body with braces as three lines, not one. void also makes the intention to not return explicit, while just omitting the return statement can also be by mistake.

@alinnert
Copy link

alinnert commented Apr 2, 2020

Okay, that makes sense. Thanks.

@avindra
Copy link

avindra commented Apr 5, 2020

Actually readonly is a not a great choice for UX, since most browsers seem to prevent you from copying the text in the input.

I guess that can be changed at a browser level in the future, but if you just use the void trick, you can get the desired behavior today...

@G-Rath
Copy link

G-Rath commented Jul 7, 2020

I opened an GH issue based on the original one linked to by this gist to adjust no-void to support the use-cases described here, which the ESLint team are currently evaluating. Here's the GH issue to check out if folks want to weigh in or +1 to show that there's interest in having the change made :)

@codemaster138
Copy link

To add to this list:

undefined is a valid variable name and can be shadowed

The following is valid javascript:

let undefined = 'defined';
console.log(undefined) // logs "defined"

In this case void 0 is the only way to get a reference to the actual undefined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment