TIL: Destructuring from an Object with a possibly Undefined Property
Destructure from a possibly undefined object property
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
.
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
,””
orNaN
).
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.
// 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, becausenull
andundefined
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
.
//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:
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)