Clean up your code with destructuring
Destructuring is a neat way of cleaning up JavaScript code. In this post we’ll go through lots of different areas destructuring helps the readability of our code.
Function parameters
When a function parameter is an object rather than a primitive, destructuring can be used to break the object into the parts that the function references. The code below is without any destructuring:
async function logData(options) {
if (!options.skip) {
const response = await fetch(options.path);
const data = await response.json();
console.log(data);
}
}
This can be refactored into the following by destructuring the options
parameter:
async function logData({ skip, path }) {
if (!skip) {
const response = await fetch(path);
const data = await response.json();
console.log(data);
}
}
This is arguably cleaner because we are referencing the parameter properties, skip
and path
, directly rather than through the options
parameter name.
A really neat thing about this approach is that we can apply default values to the destructured properties. Without destructuring we’d have some like this:
async function logData(options) {
const path = options.path || "https://swapi.co/api/people/1";
if (!options.skip) {
const response = await fetch(path);
const data = await response.json();
console.log(data);
}
}
… but with destructuring, its a little cleaner:
async function logData({ skip, path = "https://swapi.co/api/people/1" }) {
if (!skip) {
const response = await fetch(path);
const data = await response.json();
console.log(data);
}
}
Apart from saving a line of code, having the default value for the parameter right next to its declaration helps us understand the parameter a little easier.
This approach is useful in React function components for the components props:
export const CountDown = ({ start = 10 }) => {
const [count, setCount] = useState(start);
...
};
start
is a prop in CountDown
component in the above example.
A rest parameter can be used to collect properties that haven’t been destructured. This is useful for passing through props to lower level elements in React components:
const Field = ({ id, label, value, type = "text", onChange, ...rest }) => {
return (
<div>
<label for={id}>{label}</label>
<input type={type} id={id} value={value} onChange={onChange} {...rest} />
</div>
);
};
Destructuring can be applied to parameter properties that themselves are objects as well. The following is an example without destructuring:
async function sendEmail(config) {
const response = await fetch(config.api.path, {
method: "post",
body: JSON.stringify({
emailAddress: config.contact.emailAddress,
template: config.template
})
});
return response.ok;
}
… which can be refactored to the following:
async function sendEmail({
api: { path },
contact: { emailAddress },
template
}) {
const response = await fetch(path, {
method: "post",
body: JSON.stringify({
emailAddress,
template
})
});
return response.ok;
}
… and with some defaults:
async function sendEmail({
api: { path = "https://some-email-server" },
contact: { emailAddress },
template = "standard"
}) {
const response = await fetch(path, {
method: "post",
body: JSON.stringify({
emailAddress,
template
})
});
return response.ok;
}
We can use TypeScript to strongly-type destructured elements as follows:
async function logData({
skip = false,
path = "https://swapi.co/api/people/1"
}: {
skip?: boolean,
path?: string
}) {
if (!skip) {
const response = await fetch(path);
const data = await response.json();
console.log(data);
}
}
… or we can extract the type out of the function:
type LogDataOptions = {
skip?: boolean,
path?: string
};
async function logData({
skip = false,
path = "https://swapi.co/api/people/1"
}: LogDataOptions) {
if (!skip) {
const response = await fetch(path);
const data = await response.json();
console.log(data);
}
}
Function call result
The result of a call to a function can be destructured. So, without destructuring we may have something like this:
const character = await getCharacter(1);
console.log(character.name);
… which can be refactored to the following with destructuring:
const { name } = await getCharacter(1);
console.log(name);
Defaults can be applied in the same way as for function parameters:
const { name = "Unknown" } = await getCharacter(1);
console.log(name);
The destructured elements can be strongly-typed with TypeScript as well:
const { name = "Unknown" }: { name: string } = await getCharacter(1);
console.log(name);
… or using a reusable type:
type Character = {
name: string;
height: string;
gender: "male" | "female";
...
}
...
const { name = "Unknown" }: Character = await getCharacter(1);
console.log(name);
Destructured items can be renamed as follows:
const { name: characterName } = await getCharacter(1);
console.log(characterName);
… and with a TypeScript type annotation:
const { name: characterName }: { name: string } = await getCharacter(1);
Notice that the type represents the data before it’s property been renamed.
Arrays can also be destructured. In fact, this is used to consume React hooks:
count
is the first array item returned by the useState
hook and setCount
is the second.
Array items can also be skipped in the destructuring process:
const [, , z] = getPoint();
In the above example we skip past the first two array items and assign the third item to z
.
Wrap up
Destructuring syntax may seem weird at first but you quickly get used to it and it’s a neat little way of making code a little cleaner and more readable.