6 Awesome Tricks with the Spread and Rest Operators in Typescript and Javascript Objects
Table of Contents
Introduction
You may have seen this syntax in Typescript and Javascript Projects before:
const {id, name, age, ...props} = obj
if you have used React, then you have very likely encountered this syntax a couple of times when passing the props from one component to another.
The support for the spread and rest operators was added since:
Javascript with ES6 specifications and onwards
As you are reading this article, the support for those operators has been there for a very long time, and whatever environment you’re using pretty much supports it now.
The spread and rest operators have a number of uses that can make manipulating an object a joy, and here are 6 ways to do so!
1 Clone an Object with Spread Operator
There will be times when you want to modify an object, but without losing the original object data.
The problem
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const anotherCat = cat;// ❌ this will only create a reference of "cat" anotherCat.age = 5; console.log(cat);
Doing this will only create a reference of our cat
object, meaning they are one and the same in memory. Modifying either of them will update the same memory allocation.
What if we wanted to modify anotherCat
without cat
being affected?
Spread operator to the rescue!
with the spread operator, we can quickly create a clone of our object!
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const copiedCat = { ...cat }; // âś… this will create a clone (new instance) of our variable copiedCat.age = 5; // and we can change its values without affecting the original variable console.log("original cat", cat); console.log("copied cat", copiedCat);
2 Extract Values from an object
This one isn’t directly about the spread and rest operators, but it will build the knowledge required for our next point!
We can extract specific properties from our object by doing so:
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const { name, age } = cat; console.log("my cat's name is " + name);
what you have to take note of when extracting properties is we are creating new instances for everything. This means if we modify cat
after extracting its properties, then the changes won’t be reflected in the extracted properties.
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const { name, age } = cat; cat.name = "luna"; // value assignment after extracting the properties console.log("my cat's name is " + name);
So, if we wanted to have this change apply to our extracted properties, we have to apply them before extracting the properties like so:
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; cat.name = "luna"; // value assignment before extracting the properties const { name, age } = cat; console.log("my cat's name is " + name);
3 Exclude Values from an object with Rest Operator
This is a cool one. What if we wanted to remove some of the properties?
this is where the reset operator comes in
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const { favoriteFood, age, ...newCat } = cat; console.log(newCat);
the rest operator here means “combine the rest of the properties in a new object”. and it will give us this result:
and just like extracting properties, modifying cat
properties will not update the values for newCat
, as it is a completely new object:
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const { favoriteFood, age, ...newCat } = cat; cat.name = "luna"; // this will not update newCat name console.log(newCat);
Spread Operator vs Rest Operator in Typescript and Javascript
The Spread Operator
The spread operator is found on the right-hand side of the object assignment.
It spreads the object keys in the sense that it iterates over each property:
const cat = { name: "chase", type: "cat", color: "black", }; const anotherCat = {...cat} // 👆 here, we are using the spread operator. // what we are doing here is iterating over each key: // name:"chase", type:"cat", color:"black"
This is what we are doing if we wanted to look at it in a “literal” sense:
const cat = { name: "chase", type: "cat", color: "black", }; const anotherCat = {name:"chase", type:"cat", black:"black"}
now It makes more sense how the spread operator is creating a new instance of the object.
The Rest Operator
The rest operator is found on the left-hand side of the object assignment.
with the rest operator, we are doing the opposite. we are collecting all of the properties in a single object.
const cat = { name: "chase", type: "cat", color: "black" }; const { type } = cat; // we extracted type, but we can still extract "name" and "color"
now if we wanted to combine the remaining name
and color
properties, then we will use the rest operator like in our previous example.
const cat = { name: "chase", type: "cat", color: "black" }; const { type, ...newCat } = cat;
and a “literal” sense, this is what we are doing:
const cat = { name: "chase", type: "cat", color: "black" }; const { type } = cat; const newCat = { name: "chase", color: "black" };
So from those examples, I believe you can understand that we are using the spread and rest operators to clone properties and their values from an existing object, rather than writing them down manually.
Okay, but what happens if we use the rest operator without extracting any properties?
Bonus: Another way to clone an object
Now that we are aware of what the rest operator is doing, we can also come to this conclusion:
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const catCopy = { ...cat }; // this creates a clone by using spread operator! const { ...anotherCopy } = cat; // this also creates a clone by using rest operator! catCopy.name = "lulu"; anotherCopy.name = "zoey"; console.log(cat.name, catCopy.name, anotherCopy.name);
Both syntaxes work to achieve the same goal to clone an object
4 Combine objects with Spread Operator
let us say that we have an object that we want to change some of its properties, we can use the spread operator to do so:
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const anotherCat = { name: "luna", type: "bengal", age: 5 }; const combinedCat = { ...cat, ...anotherCat }; console.log(combinedCat);
as you can see, we have changed the name
, type
, and age
properties of the cat
object with the ones that were provided by anotherCat
.
A few things to know:
- Once again, we are creating a new object, and not referencing either of the original objects.
- The last object’s values are the ones that will be put in the combined object.
Combine Multiple Objects
To elaborate more on the second point, this means that if we were to have a third object in there, then its values are the ones that will apply in the combined object.
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const anotherCat = { name: "luna", type: "bengal", age: 5 }; const thirdCat = { name: "cello" }; const combinedCat = { ...cat, ...anotherCat, ...thirdCat }; console.log(combinedCat);
Think of it like this:
anotherCat
values will be applied on top of cat
then, thirdCat
values will be applied on top of the modified cat
Combine objects with different properties
combining with the spread operator does not restrict us to having the exact same properties of the first object.
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const anotherCat = { name: "luna", type: "bengal", age: 5, favoriteToy: "snuggles", favoriteSpot: "my couch" }; const combinedCat = { ...cat, ...anotherCat }; console.log(combinedCat);
this way, the properties that didn’t exist with cat
will be added in the combinedCat
object!
5 Change Multiple Properties
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const newCat = { ...cat, name: "cello", age: 6, type: "sphynx" }; console.log(newCat)
we can also combine this with our previous example!
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const secondCat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "wet food" }; const newCat = { ...cat, ...secondCat, name: "cello", age: 6, type: "sphynx", isHappy: true }; console.log(newCat);
6 Add new properties to an object with Spread Operator
If we wanted to add a property that didn’t exist in the object before:
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; cat.isHappy = true; // ❌ this will **NOT** work
Then we will get the following error:
Property 'isHappy' does not exist on type '{ name: string; type: string; color: string; age: number; favoriteFood: string; }'.ts(2339)
to work around this issue, we can use the spread operator to create a new object with the new property!
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const modifiedCat = { ...cat, isHappy: true }; console.log(modifiedCat);
To really understand this in Typescript, if we were to make an interface, we can see that the new object generated by the spread operator does NOT have the original interface:
interface Cat { name: string; type: string; color: string; age: number; favoriteFood: string; } const cat: Cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const newCat = { ...cat };
this means we are creating a new object without the constraints of our Cat
interface.
so if we wanted to retain the type safety of our cloned object, then we will need to explicitly say that it is of type Cat
.
interface Cat { name: string; type: string; color: string; age: number; favoriteFood: string; } const cat: Cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const newCat:Cat = { ...cat, isHappy:true }; // however, this will no longer work.
and if we really wanted everything to have a proper interface for it, then we can do something like this:
interface Cat { name: string; type: string; color: string; age: number; favoriteFood: string; } interface HappyCat extends Cat { isHappy: boolean; } const cat: Cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const newCat: HappyCat = { ...cat, isHappy: true }; // this will work!
Bonus: everything together!
Let us recap by using everything we just learned!
this is a more advanced example where we’ll be using multiple things together:
const cat = { name: "chase", type: "cat", color: "black", age: 2, favoriteFood: "fish" }; const updateCatObject = { name: "chase", type: "cat", color: "white", age: 4, favoriteFood: "wet catfood" }; // rest operator to combine the remaining properties const { name, type, ...updateCat } = updateCatObject; const catWithNewProp = { ...updateCat, favoriteSpot: "sofa" }; const combinedCat = { ...cat, // spread operator, spreading the name, type, color, age and favoriteFood properies ...catWithNewProp, //spread operator, spreading the color, age, favoriteFood, favoriteSpot name: "lulu", // modifing an existing property lovesLasers: true // adding a new property }; const newCat = { ...combinedCat }; // spreading our entire combined object newCat.name = "cello"; console.log(newCat); console.log(combinedCat);
before you have a look at the output, I’d want you to have a moment to think about what the result of newCat
and combinedCat
will be. See if you can get it right.
Conclusion
The spread and rest operators are very powerful tools to manipulate objects. You will very likely be using it in your Typescript and Javascript apps. So make sure you get comfortable with it! You will likely use them very often in your react and express apps.
Sing up to get an email notification when new content is published