Why { …defaultValues, …newValues } can hide a bug

Rmag Breaking News

Using the spread operator to create a new object using other objects is not unusual, but what happens if both objects have the same key?

TL;DR: This article explores a common issue with the spread operator in JavaScript when merging objects with overlapping keys and potentially falsy values. It explains how the spread operator can override default values with null or falsy values, leading to unexpected behavior. The article offers two solutions: a simpler one that directly checks and assigns values based on truthiness and a more scalable approach using Object.entries and Object.fromEntries to filter out falsy values before merging.

The scenario

You are creating a new object in Javascript with default values and want to update it with new values from another object.

Knowing that JavaScript has spread operator since ES6, you use the following structure.

const defaultValues = {
foo: Bar,
number: 30,
fruit: banana
}

const newValues = {
foo: Baz,
number: 40,
}

const outputValues = {
defaultValues,
newValues
}

What is the expected output?

You are right if you say that the newValues object will update the outputValues object.

// { foo: “Baz”, number: 40, fruit: “banana” }

Great, so it’s done, right?!
Hmm… maybe not

The issue

When using the spread operator to create a new object, we are not making any validation about the values coming from the newValues object, which can cause some weird results.

The possible problem

If you need to have the defaultValuesobject to be replaced only by truthy Javascript values, you have a bug in the previous code.

Given the same last scenario, but a different object newValues.

const defaultValues = {
foo: Bar,
number: 30,
fruit: banana
}

const newValues = {
foo: Baz,
number: 40,
fruit: null
}

const outputValues = {
defaultValues,
newValues
}

What is the expected output?
If you expect to have fruit: “banana” think twice.

The final value will be fruit: null.

Why? 👀

Based on the spread operator MDN definition

Overriding properties
When one object is spread into another object, or when multiple objects are spread into one object, and properties with identical names are encountered, the property takes the last value assigned while remaining in the position it was originally set.

This means that the spread operator does not validate if the value is or isn’t a truth value.

And how do we solve it?

Well, there’s the simple, non-scalable solution and the more complex, but scalable solution. Let’s discuss each of them.

Simpler solution

Let’s create a simple validation for the fruit key.
Getting back to the last scenario, but with a change in the fruit value.

const defaultValues = {
foo: Bar,
number: 30,
fruit: banana
}

const newValues = {
foo: Baz,
number: 40,
fruit: null
}

const outputValues = {
defaultValues,
newValues,
fruit: newValues.fruit || defaultValues.fruit
}

Ok, it can work in a few validations but is not scalable at all, so let’s create a validation for each value in the newValues object.

Scalable solution

To make it scalable for whatever the object size, let’s use the methods entries from Object, to parse the values.
Using this method, we will have an array of [key, value], that we can use the arrays method filter to check if the second element is truth.

Object.entries(newValues).filter(item => Boolean(item[1]))
// [ [‘foo’, ‘Baz’], [‘number’, 40] ]

Perfect, now we have validations for falsy values in the object, but I need an object, not an array, genius.

Calm down, there’s another Object method so solve it, the fromEntries method.
From MDN: “The Object.fromEntries() static method transforms a list of key-value pairs into an object.”, and that’s the magic, take a look.

const defaultValues = {
foo: Bar,
number: 30,
fruit: banana
}

const newValues = {
foo: Baz,
number: 40,
fruit: null
}

const outputValues = {
defaultValues,
Object.fromEntries(
Object.entries(newValues)
.filter(item => Boolean(item[1]))
),
}

What is the expected output?
Yes, you’re right, now the output is the same as the simpler solution, but you can use in bigger objects.
The final value is: {foo: ‘Baz’, number: 40, fruit: ‘banana’}.

Conclusion

In conclusion, while the spread operator in JavaScript offers a convenient way to merge objects, it can lead to unexpected behavior when dealing with overlapping keys and potentially falsy values. The issue arises because the spread operator does not discriminate based on the truthiness of merged values. This can result in undesired outcomes, such as overriding default values with null or other falsy values.

Check out my links

Website: https://www.guimoraes.dev/
Linkedin: https://www.linkedin.com/in/guimoraesdev/

If you find any error or just want to say hi, let a comment below.
See ya! 👋

Leave a Reply

Your email address will not be published. Required fields are marked *