Boosting Code Readability

Artigo em português

Most of the tips presented in this article will rely on ES6 (ECMAScript 2015) syntax

asdfsd

Destructuring

Destructuring can be useful for retrieving individual items from an object or an array. Be careful when using arrays as you can get easily confused by renaming some property in the wrong way, let's take a look at one example:


_14
// students => [age, grade]
_14
const students = [
_14
[19, 80.9],
_14
[21, 75.2],
_14
[19, 88.7]
_14
]
_14
_14
students.forEach(([age]) => age) // 19, 21, 19
_14
_14
// Even renaming the first value as a grade, we get their age
_14
students.forEach(([grade, age]) => grade) // 19, 21, 19
_14
_14
// We can skip array values using a comma (,)
_14
students.forEach(([, grade]) => grade) // 80.9, 75.2, 88.7

Now using an object.


_14
const students = {
_14
Kevin: {
_14
age: 19,
_14
grade: 80.9
_14
},
_14
Laura: {
_14
age: 21,
_14
grade: 75.2
_14
}
_14
}
_14
_14
const { Kevin } = students
_14
_14
Kevin.grade // 80.9

Using property accessors

We can have access to an object property in two ways, you can use the simple dot ., or the bracket [] access notation.


_6
const Kevin = {
_6
age: 19,
_6
grade: 80.9
_6
}
_6
_6
Kevin['grade'] // 80.9

An example with CSS modules: using a class name with special characters.


_3
const Text = () => {
_3
return <p className={styles['primary-text']}>Hello!</p>
_3
}

If you want, you can also access dynamically properties:


_9
const statusMessages = {
_9
ERROR: 'RIP server',
_9
LOADING: 'We are trying our best',
_9
IDLE: 'Waiting for input'
_9
}
_9
_9
const status = getCurrentStatus() // LOADING
_9
_9
statusMessages[status] // We are trying our best

Using object function parameters

This is situational, but usually, when I get more than 2 or 3 parameters in a function, I use this method. Compare the functions below:


_12
const getRandomArray = (arrayLength, contentVariation, delayInMS) =>
_12
new Promise((resolve) => {
_12
const randomNumber = () => Math.ceil(Math.random() * contentVariation)
_12
_12
const generatedArray = Array(arrayLength)
_12
.fill(0)
_12
.map((_) => randomNumber())
_12
_12
setTimeout(() => resolve(generatedArray), delayInMS)
_12
})
_12
_12
getRandomArray(8, 5, 2000).then((array) => array) // [4, 4, 5, 1, 4, 5, 1, 4]

I'm sorry, I couldn't think of anything better than this. But let's now take a look at the object notation.


_4
const getRandomArray = ({ arrayLength, contentVariation, delayInMS }) =>
_4
new Promise((resolve) => ... );
_4
_4
getRandomArray({ arrayLength: 8, delayInMS: 2000, contentVariation: 5 });

Now, we don't get lost in our parameters and we don't need to worry about correctly ordering them.

Dictionaries

Sometimes we want to translate some returns or get a message by status. Dictionaries can easily do this without writing a bunch of if statements. They were previously mentioned in a code block before so let's rewind.


_30
const ifStatement = (status) => {
_30
if (status === 'ERROR') return 'RIP server'
_30
if (status === 'LOADING') return 'We are trying our best'
_30
if (status === 'IDLE') return 'Waiting for input'
_30
}
_30
_30
ifStatement('ERROR')
_30
_30
const switchCases = (status) => {
_30
switch (status) {
_30
case 'ERROR':
_30
return 'RIP server'
_30
case 'LOADING':
_30
return 'We are trying our best'
_30
case 'IDLE':
_30
return 'Waiting for input'
_30
default:
_30
throw new Error(`Status: ${status}, not found`)
_30
}
_30
}
_30
_30
switchCases('ERROR')
_30
_30
const dictionary = {
_30
ERROR: 'RIP server',
_30
LOADING: 'We are trying our best',
_30
IDLE: 'Waiting for input'
_30
}
_30
_30
dictionary['ERROR']

Arrow functions

Arrow functions have their disadvantages when compared with normal ones, but you can get a clean result using them. Look at some comparisons below.


_16
// Function version
_16
function doubleValueF(value) {
_16
return value * 2
_16
}
_16
_16
function getPeopleNameF(people) {
_16
return people.map((person) => person.name)
_16
}
_16
_16
// Arrow function version
_16
const doubleValueA = (value) => value * 2
_16
_16
const getPeopleNameA = (people) => people.map((person) => person.name)
_16
_16
// Destructuring
_16
const getPeopleNameA = (people) => people.map(({ name }) => name)

Using them, we can do one-liners and omit the parenthesis around the parameters in case there's only one. In line 27, we combine destructuring with arrow functions.

Guard clauses - Avoiding else keyword

When we use if statements, we intend to cover every case we need, consequently flooding our code with else keywords. Let's take this example that uses the return keyword look:


_9
const checkAge = (age) => {
_9
if (age < 18) {
_9
return 'Cannot enter in the party.'
_9
} else if (age >= 18 && age < 21) {
_9
return "Can enter, but can't drink."
_9
} else {
_9
return 'Can enter and drink.'
_9
}
_9
}

In the checkAge function we check for the input, and the logic work's like this:

We have a few issues in this code that we can correct:

Let's refactor and check it out.


_6
const checkAge = (age) => {
_6
if (age < 18) return 'Cannot enter in the party.'
_6
if (age < 21) return "Can enter, but can't drink."
_6
_6
return 'Can enter and drink.'
_6
}

Look how we can both use the line 2 or line 3-4 ways to return the message, so use what looks better for you. Personally, for long messages, I use the second method. In conclusion, evaluate if you need to use a keyword.

When we can't avoid the else keyword

Usually, we can't avoid the use of else if we need to return the same thing for both conditions.


_6
const checkUserRoles = (user) => {
_6
if (user.isPremium) renderPremiumStats()
_6
else renderAds()
_6
_6
return renderApplicationInterface()
_6
}

Spread operator to pass React Props

This operator is responsible to get the rest of an object, let's suppose that we need to map through a list of users and return a user card. This is the user's list:


_10
const users = [
_10
{
_10
name: "George",
_10
age: 72,
_10
weigh: 180,
_10
heigh: 5.8,
_10
job: "Trucker"
_10
},
_10
...
_10
]

And now the component and the props.

Without spread operator:


_6
const UserCard = ({ name, age, job }) => <></>
_6
_6
const App = () =>
_6
users.map(({ name, age, job }) => (
_6
<UserCard name={name} age={age} job={job} />
_6
))

Using spread operator:


_1
const App = () => users.map((user) => <UserCard {...user} />)

Look how without needing all the props, we still can pass the spread operator to the component. Even destructuring the props in the first example, our second code block gives much more readability.

Short Circuit Evaluation

Sometimes we just need a short conditional to define a variable value or check a conditional, and with if statements we spend too much space on simple things. Look at these two examples:


_6
// if statement
_6
if (isSunny) chosenClothing = SummerClothing
_6
else chosenClothing = WinterClothing
_6
_6
// short circuit
_6
const chosenClothing = isSunny ? SummerClothing : WinterClothing



_5
...
_5
_5
if (isSunny && isHappy) return goToBeach()
_5
_5
...

For more information about short circuits check for additional resources at the end of the article.

Avoid short circuits in React

Yes, I know that I recommended using short circuits in this article, but sometimes avoiding them can give us better readability in our code. Let's look at some examples

Short circuit


_17
const Component = ({ isAdmin }) => {
_17
const [state, setState] = useState(false)
_17
_17
return isAdmin ? (
_17
<AdminInterface>
_17
<>
_17
<></>
_17
</>
_17
</AdminInterface>
_17
) : (
_17
<UserInterface>
_17
<>
_17
<></>
_17
</>
_17
</UserInterface>
_17
)
_17
}

If statements


_20
const Component = ({ isAdmin }) => {
_20
const [state, setState] = useState(false)
_20
_20
if (isAdmin)
_20
return (
_20
<AdminInterface>
_20
<>
_20
<></>
_20
</>
_20
</AdminInterface>
_20
)
_20
_20
return (
_20
<UserInterface>
_20
<>
_20
<></>
_20
</>
_20
</UserInterface>
_20
)
_20
}

Individual components


_7
const Component = ({ isAdmin }) => {
_7
const [state, setState] = useState(false)
_7
_7
if (isAdmin) return <AdminInterface />
_7
_7
return <UserInterface />
_7
}

Individual components and no internal logic


_2
const Component = ({ isAdmin }) =>
_2
isAdmin ? <AdminInterface /> : <UserInterface />

Exporting all from index file

That's a cool import/export trick, which has nothing to do with our real code, but we can get interesting results using it. That's how we usually import files:


_3
import Brain from './Brain.png'
_3
import BriefCase from './BriefCase.png'
_3
import BuildingConstruction from './BuildingConstruction.png'

Now using an index file inside the images folder:

index.js
Copy

_17
import Brain from './Brain.png';
_17
import BriefCase from './BriefCase.png';
_17
import BuildingConstruction from './BuildingConstruction.png';
_17
import CowboyHat from './CowboyHat.png';
_17
import CrystalBall from './CrystalBall.png';
_17
import Desktop from './Desktop.png';
_17
...
_17
_17
export {
_17
Brain,
_17
BriefCase,
_17
BuildingConstruction,
_17
CowboyHat,
_17
CrystalBall,
_17
Desktop,
_17
...
_17
};

component.js
Copy

_1
import { Brain, BriefCase, BuildingConstruction } from 'public/Emojis'

Look how we transformed 3 lines into 1. Note that we don't need to use public/Emojis/index, JavaScript automatically imports the file if its name is index.

Transforming complicated conditionals into variables

Look at one example in which we are checking for multiple conditionals to verify if we can render the management panel:


_2
if (user.roles.includes('ADMIN') || (user.age > 18 && user.reputation > 10))
_2
return renderManagementPannel()

Let's refactor the code transforming the complicated conditionals into variables:


_4
const isAdmin = user.roles.includes('ADMIN')
_4
const responsibleUser = user.age > 18 && user.reputation > 10
_4
_4
if (isAdmin || responsibleUser) return renderManagementPannel()

Look how we increase lines in our code, but our team working on the application can easily know why the renderManagementPannel function is being fired.