TIL: Destructuring from Object | Undefined
Tags: todayilearned, javascript, todayisearched, codenewbie
Published: 2021-01-05 11:45:08

# Destructure from a value that can be undefined

## Problem

The **`updateUserProfile`** function allows me to retrieve the **`user`** by making a query to the database with the provided **`user_id`**, then use another database query to guarantee that the provided **`email`** does not belong to another user and finally update the user in the database sending a query with the provided **`name`** and **`email`**.

**The problem** was that when checking if we already had a user with the provided **`email`** in the database the result could be a **`User`** object or **`undefined`**, depending on if we found the user or not.

The **`User`** object contains many properties (**`id`**, **`name`**, **`email`**, **`avatar`**, **`password`**, **`created_at`**, **`updated_at`**), but I only needed the **`id`** property to compare with the provided **`user_id`** to guarantee that the email did not belong to any user.

I was not able to use **destructuring** to unpack only the **`id`**  from the **result** nor rename it to **`findEmailOwner`** because the result could be a **`User`** object or **`undefined`**, so I got the following **TypeScript error message**: **`"Property 'id' does not exist on type 'User | undefined'`**.

**TLDR**: I need to obtain **`id`** by **destructuring** a **value** that can be an **`object`** or **`undefined`**.

```tsx
function updateUserProfile ({ user_id, name, email }) {
  const user = await usersRepository.findById(user_id);

  if (!user) {
    throw new AppError(`The user was not found.`, 401);
  }

  const { id: findEmailOwner } = await usersRepository.findByEmail(email); // error message: "Property 'id' does not exist on type 'User | undefined'.

  if (typeof findEmailOwner !== 'undefined' && findEmailOwner !== user_id) {
    throw new AppError(`This email cannot be used.`, 401);
  }

  user.name = name;  
  user.email = email;

  return usersRepository.save(user);
}
```

## Answer

- We can use **short circuit evaluation** to supply a **default** if **`user`** is a **falsy value** (**`undefined`**, **`null`**, **`0`**, **`-0`**, **`0n`**, **`""`** or **`NaN`**).

**NOTE 1**: I can use this approach in my application because the **`id`** **property** that I want to retrieve with destructuring **cannot be assigned to any falsy value in my database**.

**NOTE 2**: BUT if I was retrieving the **`avatar` property** that can be assigned to **`null`** in the database, this approach would not work.

    ```tsx
    // Case 1 - id (cannot contain falsy values)

    // user does not exist
    const user = undefined
    const { id } = user || {}
    console.log(id) // undefined (what we expect)

    // user exists
    const user = {
    	id: 'aaaa-aaaa-aaaa-aaaa',
    };
    const { id } = user || {}
    console.log(id) // 'aaaa-aaaa-aaaa-aaaa' (what we expect)

    // Result: SUCCESS

    //-----------------------------------------

    // Case 2 - avatar (can contain null a falsy values)

    const user = undefined
    const { avatar } = user || {}
    console.log(avatar) // undefined (what we expect)

    const user = {
    	avatar: 'photo.jpg',
    };
    const { avatar } = user || {}
    console.log(avatar) // 'photo.jpg' (what we expect)

    const user = {
    	avatar: null,
    };
    const { avatar } = user || {}
    console.log(avatar) // undefined (not good, we needed this to be null)

    // Result: FAILURE
    ```

- Another approach is to spread the **`user`** into an object before destructuring it, because **`null`** and **`undefined`** **values** are ignored.

**NOTE 1**: I would use this approach if was retrieving the **`avatar` property** that can be assigned to a **falsy value** (**`null`**) in the database since the first approach would not work.

**NOTE 2**: This approach is **less idiomatic**, so I would not use it for cases where the first approach works.

**NOTE 3**: This approach would also work for **`id`**.

```tsx
//Case - avatar (can contain null a falsy values)

const user = undefined
const { avatar } = { ...user }
console.log(avatar) //undefined (what we expect)

const user = {
  avatar: 'picture.jpg',
}
const { avatar } = { ...user }
console.log(avatar) // 'picture.jpg' (what we expect)

const user = {
  avatar: null,
}
const { avatar } = { ...user }
console.log(avatar) // null (what we expect)

// Result: SUCCESS
```

**Applying the short circuit evaluation approach to our code**: 

```tsx
function updateUserProfile ({ user_id, name, email }) {
  const user = await usersRepository.findById(user_id);
  if (!user) {
    throw new AppError(`The user was not found.`, 401);
  }
  const { id: findEmailOwner } = (await usersRepository.findByEmail(email)) || {}; // 1st approach
  if (typeof findEmailOwner !== 'undefined' && findEmailOwner !== user_id) {
    throw new AppError(`This email cannot be used.`, 401);
  }
  user.name = name;
  user.email = email;
  return usersRepository.save(user);
}
```

## TLDR

- Retrieving a **property** (that cannot be falsy) with destructuring from a value that can be an **`object`** or **`undefined`** - use **short circuit evaluation**.
- Retrieving a **property** (that can be falsy) with destructuring from a value that can be an **`object`** or **`undefined`** - use the **spread operator** on the **value** that can be an object or undefined.

## Additional Links

- [JS/ES6: Destructuring of undefined on Stack Overflow](https://stackoverflow.com/questions/48433008/js-es6-destructuring-of-undefined)

# Keep in touch
Contact me through my social media. Let's talk about DDD, TDD, good practices and the new Wonder Woman 1982 movie, be it on [LinkedIn](https://www.linkedin.com/in/stevescruz/) or [GitHub](https://github.com/stevescruz).

Tell me what you learned today.