210

I'm using some destructuring like this:

const { item } = content
console.log(item)

But how should I handle content === undefined - which will throw an error?

The 'old' way would look like this:

const item = content && content.item

So, if content is undefined -> item will also be undefined.

Can I do something similar using destructuring?

user3142695
  • 15,844
  • 47
  • 176
  • 332
  • See also [JS destructuring. How to deal with null or undefined values](https://stackoverflow.com/questions/47390089/js-destructuring-how-to-deal-with-null-or-undefined-values), [What does passing arguments in this way mean?](https://stackoverflow.com/questions/48293341/what-does-passing-arguments-in-this-way-mean/) – guest271314 Jan 24 '18 at 23:19

8 Answers8

379

You can use short circuit evaluation to supply a default if content is a falsy value, usually undefined or null in this case.

const content = undefined
const { item } = content || {}
console.log(item)                       // undefined

A less idiomatic (see this comment) way is to spread the content into an object before destructuring it, because null and undefineds values are ignored.

const content = undefined
const { item } = { ...content }
console.log(item) // undefined

If you are destructuring function params you can supply a default (= {} in the example).

Note: The default value would only be applied if the destructured param is undefined, which means that destructuring null values will throw an error.

const getItem = ({ item } = {}) => item
console.log(getItem({ item: "thing" })) // "thing"
console.log(getItem())                  // undefined

try {
  getItem(null)
} catch(e) {
  console.log(e.message)                // Error - Cannot destructure property `item` of 'undefined' or 'null'.
}

Or even set a default value for the item property if the input object doesn't contain the property

const getItem = ({ item = "default" } = {}) => item
console.log(getItem({ item: "thing" })) // "thing"
console.log(getItem({ foo: "bar" }))    // "default"
Qtax
  • 33,241
  • 9
  • 83
  • 121
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • 2
    "*You can also spread the content into an object before destructuring it*" - this seems both very inefficient and unidiomatic to me – Bergi Sep 06 '18 at 14:06
  • 1
    @Bergi - I'll not argue about the efficiency as it depends on what you're doing with it. for loop is more efficient than forEach, but we still use the latter unless we need every ounce of performence. Why is it `unidiomatic` in your opinion? – Ori Drori Sep 06 '18 at 14:14
  • 1
    (We use `for of` because it reads nicer and works better than `.forEach`, but they have [the same speed in today's engines](https://youtu.be/EhpmNyR2Za0?t=17m15s)). It feels unidiomatic because it just… looks weird in my eyes. `{ ...content }` is known as an idiom for "make a shallow copy of `content`", not "…or an empty object when it is undefined". – Bergi Sep 06 '18 at 14:23
  • 3
    I think of it as a potential use for spread. It's less readable than short circuit, but it's another tool in the toolbox. – Ori Drori Sep 06 '18 at 14:27
  • ``` const content = undefined const { item } = content ?? {} console.log(item) ``` – phifa Apr 12 '22 at 08:41
27
const { item } = Object(content)
14

Destructuring the nested object is clean and short but sucks when source property is null or undefined in right side object

let say we have

const {
  loading,
  data: { getPosts },
} = useQuery(FETCH_POSTS_QUERY);

Solution 1 if we have data object but no getPosts then we can use:
(Setting default at each level)

const {
  loading,
  data: { getPosts = [] } = { getPosts: [] },
} = useQuery(FETCH_POSTS_QUERY);

Solution 2: if event data is undefined then:

const {
  loading,
  data: { getPosts } = { getPosts: [] },
} = useQuery(FETCH_POSTS_QUERY);
Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
mabdullahse
  • 3,474
  • 25
  • 23
  • 1
    Thank you for this hint. But I lose typescript definition if data is undefined. My question is here: https://stackoverflow.com/questions/70755592/how-to-reduce-code-using-es6-destructuring-syntax. Can you help me? – Fred Hors Jan 18 '22 at 14:41
7

There is recently added: Nullish coalescing operator (??).
Which basically returns right-side value, if the left-side value is null or undefined (our case with undefined instead of an object).

const { item } = undefined or null
// Uncaught TypeError: Cannot destructure property 'item' of 'null' as it is  null.

const { item } = content ?? {}
console.log(item)   // undefined

So consider using the operator. Also, as mentioned before in the answers, there is || (or) operator. For us, there is no significant difference in this particular case.

That's just a matter of taste, in our team, we have an agreement: we use ?? for defining default object if the target object is null or undefined, and in other cases we use || operator.

Andrik Dizhak
  • 95
  • 1
  • 5
5

One can unpack undefined value, but can't unpack from undefined.
Fixing it is as easy as setting the default params value.

Example:

(() => {
    // prepare payload
    const PAYLOAD = {
        holdingJustThis: 1
    };
    // lets unpack the payload and more
    const {
        holdingJustThis,
        itIsGoingToBeUndefined,
        itCouldThrowButWont: {
            deep
        } = {}                  // this will secure unpacking "deep"
    } = PAYLOAD;

    console.log({
        holdingJustThis
    });
    console.log({
        itIsGoingToBeUndefined  // logs {itIsGoingToBeUndefined:undefined}
    });
    console.log({
        deep                    // logs {deep:undefined}
    });
})()
Marecky
  • 1,924
  • 2
  • 25
  • 39
4

I'll just add that for the OP's use case, it is also possible to use the Optional chaining operator:

const item = content?.item
console.log(item)

If content is null or undefined, then content.item will not be accessed and item will be undefined.

Papooch
  • 1,372
  • 11
  • 17
  • 1
    When I read this first, I thought it would trigger the [prefer-destructuring](https://eslint.org/docs/rules/prefer-destructuring) ESLint rule, but it seems like the optional chaining disarms that rule. This answer is therefore perfectly valid, although it doesn't use destructuring anymore, as intended by @user3142695. – Regaddi Sep 09 '21 at 13:55
  • This answer is irrelevant to the question since the topic is "destructuring". completely ignoring the problem and doing something else is easy, but solving the actual problem is hard, because of the current language limitations – vsync Jan 29 '22 at 20:06
  • @vsync I agree that this answer doesn't 100% address the original question, but I just wanted to show a different way of approaching the problem. If you only need one variable, then unpacking might be unnecessary and not everyone might think of optional chaining. I understand that it feels a like the "don't use that, use this instead" type of answer, but it might still help someone. – Papooch Jan 30 '22 at 10:14
  • Probably better as a comment than a full answer – vsync Jan 30 '22 at 11:36
2
const content = undefined
const { item } = content ?? {}
console.log(item)   // undefined           
phifa
  • 856
  • 7
  • 11
1

accepted answer does not work for truely undefined values which were not set by const content = undefined. in such cases this will work:

const { item } = (typeof content !== 'undefined' && content) || {}
console.log(item)
Nir O.
  • 1,563
  • 1
  • 17
  • 26
  • 9
    What do you mean by "truely undefined values"? – Bergi Oct 13 '19 at 21:08
  • 1
    @Bergi I believe they're talking about un_declared_ variables vs declared variables with the value of `undefined`. Testing the above code in a dev console where `content` is not declared or in scope will work, but replacing the assignment with `content || {}` throws a `Uncaught ReferenceError: content is not defined` exception. – UnknownUnknowns Aug 22 '20 at 01:07