The power of … | Spread & Rest syntax with common pitfalls and best practices
After talking about readability, let’s have a look at possible applications.
Spreading with the Spread Operator
Spreading an object allows us to grab a copy of all attributes and reassign them. For example, if you wanted to create a cube from a square but keep the existing square, you just need to add the Z-axis and your square is cubed.
Another simple yet useful case would be adding copies of 2 arrays to a third or to create a new one.
As mentioned, spreading can be used to take an existing object and create a new copy of it so let’s take a look at a more sophisticated problem. If we have a baseUnit object to store our default values and a function that creates new units with a name, we can pass the object to our function when creating new unit objects.
Let’s take a look at the following code snippet. In our
createUnitCopy() function, we’re spreading the content of a baseUnit and add a name. Even if we want to change some of the values, we can simply spread the baseUnit and add or overwrite any values we want to change, as you can see in our
createEnhancedUnitCopy() function. For our example, we are spreading the baseUnit (which already has an attribute
strength ) and we simply assign it with a new value of 23.
As you can see, neither the baseUnit’s nor the unitCopy’s values are changed. Everything is working as expected.
Pitfall: Shallow Copy
If we were to create a more complex base unit — let’s say we have more attributes and move the strength to a deeper nested level along with a few new values. Other than that, we’ll do exactly the same as in the last example.
If we run the code, we’ll see that not only Gretel became super strong, for some reason both her Brother and the baseUnit also got the boosted strength of 23.
The reason for that is pretty simple but not always easy to spot. The spread operator only creates a shallow copy. Only the top level of the spread object is copied. Everything below this level is simply a reference. The moment we spread baseUnit and changed one of the attributes’ attributes, we changed the referenced value of attributes.strength, not the individual value of our copy. This means we changed all instances of the baseUnit at once, which can seriously mess up your project and can be very difficult to debug.
The spread operator only creates a shallow copy.
This is also a common mistake I see when people start using Redux. The core idea of Redux is, that the state inside the store is immutable. If you want to change the state, you dispatch an action and create a new state.
When using Redux, you store your data inside the redux store and every action to change the state will create a new state.
This way, you have a reliable single source of truth (the store) and gain features like the ability to “time travel” through all state changes for debugging. If your reducers take the current state and spread it into newState before you make some changes, depending on the structure of the state, you might run into the same bug as we did with our createEnhancedUnitCopy() function.
This is also one of the reasons why it’s highly recommended to keep the stored data as flat as possible when working with Redux.
Taking the rest with the Rest Parameters
The rest parameter looks a lot like the spread operator but could be seen as the logical opposite. While the spread operator, well, spreads the elements, the rest parameter gathers all “remaining” parameters. This is commonly used when destructuring objects, for example in React when a component has a number of props that you want to access.
If you take a look at this simple React example, I’ll explain it real quick in case you don’t work with react. We are creating a new component called NewWorldComponent that expects props. One of the props is targetWorld which will be printed as part of the “Hello world” string. We can then call this component somewhere else with the targetWorld attribute and get a div with our output.
If we were to build a component that renders a link instead of a div, maybe we want to allow the user to add additional optional attributes, we could expand this example with the rest parameters and spread operator.
As you can see, we’re calling the same component twice. On the first call, we supply exactly the expected props url and label. On the second call, we added a third and fourth attribute and both were caught by our const additionalProps using the rest parameters which we later spread into our link.
The rest parameters can also be used to accept an indefinite number of arguments for a function like in this example from the MDN. Using the rest parameter like this, we create an array with all arguments from the function call. This means we can use all array methods like sort, map, filter or reduce.
Here’s another react-free example for using the rest parameters. In this case, returning to our fairy tale example from earlier, we are assigning Hansel during the destructuring and catch the remaining attributes of our people object with …rest.
I hope this article gave you a good idea how to use this wonderful syntactic sugar and maybe gave you an idea or two for your next project.
Some words about me:
If you want to see more of my work and progress, feel free to check out my series on building a React Native Web App with Hooks, Bells, and Whistles with Expo SDK33. I’m also currently working on a new series about building more complex and scalable apps using React/Redux, where I’ll go into details about how and why I do stuff the way I do as well as some articles on my experience in building games for web and mobile with React.
And if you feel really supportive right now, you can always support me on patreon, thus allowing me to continue to write tutorials and offer support in the comments section.