Object and Array Spread/Destructuring/Rest Usage
2019-02-22
Foreword
With the rise of ES2015 and beyond there were quite a few features introduced. The ones which I find myself using a lot are all the slice and dice features:
- Object/Array Destructuring
- Object/Array Rest
- Object/Array Spread
Here I’m not trying to explain what they do, but rather show where I found them useful in one way or the other. So, after some thinking, I’ve compiled a list.
Note most of the uses here create new throwaway objects. This might trigger garbage collection and have some performance hits. But, if used sparingly, I found these nicer on the eye once I got used to them. Your mileage might vary.
Omitting Items
One of the handy functions in lodash is omit
which allows you to return an object
without specified keys. With arrow functions and destructuring this could be
done like:
array.map(({ omit1, omit2, omit3, ...rest }) => (rest));
Here omit 1,2 and 3 are the keys which we don’t need.
Picking Items
While sometimes we want to omit a few keys, other times we need to pick just a few from the big objects. To do so we can alter the previous function:
array.map(({
pick1, pick2, pick3, ...rest
}) => ({
pick1, pick2, pick3
}));
this would provide a new object with the pick 1, 2 and 3 keys.
Transforming Object
By combining previous approaches it’s possible to transform objects, either by nesting or flattening the properties:
arr.map(({
obj1, obj2
}) => ({
...obj1, ...obj2,
}));
Conditional Adding of Items
Sometimes you want to add some properties based on a condition. This can be achieved by combining object spread and the fact that empty object doesn’t insert any new values:
const obj = {
name: 'miau',
...(condition ? { says: 'kuku' } : {})
};
Using in Reducers
Reducer is the main building block for any functional style operation. You can build filter and map and all the other from it. Because it’s versatile, there are few use-cases for spreading there.
Main Idea
You would use reducers with the concept of the accumulator. Usually, you want to use the previous accumulator value + some new data. So, in general, the idea is to spread the accumulator first and then do some computation, this looks something like:
arr.reduce((acc, obj) => ({
...acc,
// add some new properties here
}),{})
Getting all the keys from an array of mixed objects
Every now and then I need to generate CSV, rather than reaching out for the library, it’s possible to generate it by hand. One of the tasks there is getting all the titles for the columns. With the usage of object spread it can be done like so:
const keyObj = arr.reduce((acc, obj) => ({ ...acc, ...obj }), {});
const keys = Object.keys(keyObj);
Here we don’t really care about the values, so they can overwrite each other on every step.
Keying by property
In lodash, there is a function called keyBy
which takes a key name and creates
an object with keys based on the value of passed key name. This can also be done
with reduce
and object spread.
arr.reduce((acc, { key, ...rest }) => ({
...acc,
[key]: {key, ...rest}
}), {});
Note that this will have the last occurrence of the same id.
Grouping by property
Similar to the example above it’s possible to group objects with the same key.
arr.reduce((acc, { key, ...rest }) => ({
...acc,
[key]: [{ key, ...rest }, ...(acc[key] ? acc[key] : []],
}), {});
React Usage
Since react has to be transpiled it’s really easy to get started with the modern features. Some of the patterns I’ve found using rest spread in react.
React same named props
Sometimes you have a component which wraps several children, slicing and passing down the props to children. This works quite well with destructuring and spread if props have the same names:
const Child1 = ({ child1Prop1, child1Prop2 }) => (
// return jsx
);
const Child2 = ({ child2Prop1, child2Prop2 }) => (
// return jsx
);
const Parent = ({ child1Prop1, child1Prop2, child2Prop1, child2Prop2 }) => (
<>
<Child1 {...{ child1Prop1, child1Prop2 }} />
<Child2 {...{ child2Prop1, child2Prop2 }} />
</>
);
Overriding default styles
While inline styles are not generally the best idea in the world, sometimes they are the quickest way to get something out. One way to make them extendable is to allow overwriting with object spread.
const Comp = ({ styles }) => (
<div styles={{ ...defaultStyles, ...styles }}>
{/* your stuff here */}
</div>
);
Array Methods on Iterables
Sometimes you might get NodeList or something similar while working with the DOM. In order to use the filter, map and so on you need to convert iterable to an array. To do so you can simply spread the NodeList, what will give you the array you need.
[...document.querySelectorAll('p')]
.map(/* do your stuff*/)
Conclusion
Some of these will make your code easier to reason about because of immutability. But as mentioned in the beginning some of these might have an impact on performance.
In the end, you choose what’s appropriate for the use-case. I just prefer the ergonomics. And hopefully, some of these patterns will be optimized in the runtime/compiler.