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.