A spread, a rest and empty values
Tags: javascript
Published: 2019-12-07 12:31:19

Three dots syntax (`...`) became quite popular in JavaScript world in the last years. It's used for a couple of different things: object and array spread, destructuring and rest arguments. In each case, the same part keeps tricky or at least not quite intuitive - empty values. What if you want to spread an array that appears to be `undefined`? What about destructuring a `null` object?

## Object spread

```js
const foo = { ...bar, baz: 1 };
```

Spreading an object is quite a common pattern when you want to create one object based on another. In the above example, we're creating object `foo` by taking all the properties of `bar`, whatever it contains, and setting one particular property `baz` to `1`. What if `bar` turns out to be `undefined` or `null`?

```js
const bar = undefined;
const foo = { ...bar, baz: 1 };
console.log(foo);
```

```js
{ baz: 1 }
```

The answer is: nothing bad happens. The JavaScript engine handles this case and gracefully omits a spread. The same goes for `null`, you can check it by yourself. That was easy!

## Object destructuring

```js
const { baz, ...bar } = foo;
```

Destructuring an object is handy when dealing with nested data structures. It allows binding property values to names in the scope of the function or the current block. In the example above two constant values are created: `baz` equal to the value of `foo.baz` and `bar` containing all other properties of the object `foo` (that's what is called "a rest"). What happens when `foo` is an empty value?

```js
const foo = undefined;
const { baz, ...bar } = foo;
console.log(baz, bar);
```

```
Uncaught TypeError: Cannot destructure property 'baz' of 'foo' as it is undefined.
```

In this case, the JavaScript engine gives up and throws a `TypeError`. The issue here is, that non-object value (and everything except `null` and `undefined` is an object in JavaScript), simply cannot be destructured. The issue can be resolved by adding some fallback value to the statement, so the destructuring part (the left one) always gets an object.

```js
const { baz, ...bar } = foo || {};
```

This kind of error usually occurs when destructuring function arguments or nested objects. In such a case, instead of `||` operator, we can use a default parameter syntax. A caveat here is not handling the `null` value. Only `undefined` will be replaced with an empty object.

```js
function foo({
  baz: {
    qux,
    ...bar
  } = {}
} = {}) {
   // ...
}
```

## Array spread

```js
const foo = [ baz, ...bar ];
```

Similarly to the object, we can create an array based on the other. At first sight, the difference is only about the brackets. But when it comes to empty values...

```js
const bar = undefined;
const foo = [ ...bar, 1 ];
console.log(foo);
```

```
Uncaught TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
```

Unlike object spread, the array spread doesn't work for `null` and `undefined` values. It requires anything iterable, like a string, `Map` or, well, an array. Providing such a value as a fallback is enough to fix the issue.

```
const foo = [ ...(bar || []), 1 ];
```

## Array destructuring

```js
const [ baz, ...bar ] = foo;
```

Array destructuring is no different - the destructured value must be iterable. 

```js
const bar = undefined;
const [ baz, ...bar ] = foo;
console.log(baz, bar);
```

```
Uncaught TypeError: foo is not iterable
```

Again, the remedy may be `||` operator or the default argument value when it's about destructuring function parameters.


```js
const [ baz, ...bar ] = foo || [];
```

```js
function foo([
  [
    baz,
    ...bar
  ] = []
] = []) {
   // ...
}
```

To sum up - when it comes to destructuring things, we have to ensure there is always something to destructure, at least an empty object or array. Values like `null` and `undefined` are not welcome.

## Rest arguments

```js
function foo(bar, ...baz) { return [bar, baz]; }
```

In JavaScript, `...` may be found in one more place - a function definition. In this context, it means: whatever comes to the function after named arguments, put it into an array. In the above example, `bar` is a named argument of the `foo` function and `baz` is an array containing all the rest of values.

What happens when exactly one argument comes to the function or when it's called with no parameters? Is that an issue at all?

```js
foo(1);
```
```js
[1, []]
```

It is not! JavaScript engine always creates an array for the rest arguments. It also means that you can safely destructure this value without providing a fallback. The code below is perfectly valid and it's not going to fail even when `foo` is called without arguments.

```js
function foo(...bar) {
   const [baz, ...qux] = bar;
}
```

## Extra - JSX property spread

```js
const foo = <div {...bar} baz={1} />;
```

JSX is not even a JavaScript, but it shares most of its semantics. When it comes to spreading the object on the React element, empty values behave just like for object spread. Why is that so?

The code above means: create `<div>` element with a single property `baz` equal to `1` and all the properties of the object `bar`, whatever it contains. Does it sound familiar? Yes! It's nothing more than an object spread.

```js
const fooProps = { ...bar, baz: 1 };
```

When compiling JSX down to JavaScript, [Babel uses](https://babeljs.io/repl#?browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=MYewdgzgLgBAZiEMC8MA8ATAlgNxgIwEMAvZAbwEYBfGMgOgaICcaB6APgG4AoIA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2015%2Creact%2Cstage-2&prettier=false&targets=&version=7.7.5&externalPlugins=%40babel%2Fplugin-syntax-jsx%407.7.4) old-fashioned `Object.assign` function and does not create an intermediate variable, but the final effect is the same. 

```js
const foo = React.createElement("div", Object.assign({ baz: 1 }, bar));
```

So the answer is: `null` and `undefined` values are just fine when spreading on a React element. We don't need any checking or fallback values.

## The snippet

You may wonder what is the result of calling a function presented on the cover photo of this article.

```js
function foo({ bar, ...baz }, ...qux) {
    const [quux, ...quuux] = bar;
    return [{ qux, ...quux }, ...quuux];
}

foo(undefined);
```

It fails immediately on destructuring the first argument, as object destructuring requires at least an empty object. We can patch the function adding a default value for the argument.

```js
function foo({ bar, ...baz } = {}, ...qux) {
```

Now it fails on destructuring of `bar` as it's `undefined` by default and that's not an iterable thing for sure. Again, specifying a default value helps.

```js
function foo({ bar = [], ...baz } = {}, ...qux) {
```

In this form, the function works perfectly for `undefined`. What about `null`? Unfortunately, providing a fallback to both `null` and `undefined` requires at least `||` operator. The function definition becomes far less concise.

```js
function foo(barBaz, ...qux) {
    const { bar, ...baz } = barBaz || {};
    const [quux, ...quuux] = bar || [];
    return [{ qux, ...quux }, ...quuux];
}
```

And that's fine only when you don't care about other falsy values like an empty string or `0`. A more safe solution would be a ternary expression like `barBaz == null ? {} : barBaz`. Things turn complicated.

## Conclusion

Be careful when using three dots syntax with values that you're not sure about, like ones that come from backend API or third party libraries. If you're about destructuring an object or array (or spreading an array), always check against `null` and `undefined` and provide a fallback value.

In many cases, using optional chaining syntax may produce much more readable code. [Check out the performance of this syntax here](https://dev.to/erykpiast/performance-of-optional-chaining-5bpk).