Boosting Code Readability
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]_14const students = [_14 [19, 80.9],_14 [21, 75.2],_14 [19, 88.7]_14]_14_14students.forEach(([age]) => age) // 19, 21, 19_14_14// Even renaming the first value as a grade, we get their age_14students.forEach(([grade, age]) => grade) // 19, 21, 19_14_14// We can skip array values using a comma (,)_14students.forEach(([, grade]) => grade) // 80.9, 75.2, 88.7
Now using an object.
_14const students = {_14 Kevin: {_14 age: 19,_14 grade: 80.9_14 },_14 Laura: {_14 age: 21,_14 grade: 75.2_14 }_14}_14_14const { Kevin } = students_14_14Kevin.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.
_6const Kevin = {_6 age: 19,_6 grade: 80.9_6}_6_6Kevin['grade'] // 80.9
An example with CSS modules: using a class name with special characters.
_3const Text = () => {_3 return <p className={styles['primary-text']}>Hello!</p>_3}
If you want, you can also access dynamically properties:
_9const statusMessages = {_9 ERROR: 'RIP server',_9 LOADING: 'We are trying our best',_9 IDLE: 'Waiting for input'_9}_9_9const status = getCurrentStatus() // LOADING_9_9statusMessages[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:
_12const 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_12getRandomArray(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.
_4const getRandomArray = ({ arrayLength, contentVariation, delayInMS }) =>_4new Promise((resolve) => ... );_4_4getRandomArray({ 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.
_30const 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_30ifStatement('ERROR')_30_30const 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_30switchCases('ERROR')_30_30const dictionary = {_30 ERROR: 'RIP server',_30 LOADING: 'We are trying our best',_30 IDLE: 'Waiting for input'_30}_30_30dictionary['ERROR']
- If statements - Medium readability and 3 lines
- Switch cases - Medium readability and 7-9 lines
- Dictionaries - Easy readability and 3 lines
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_16function doubleValueF(value) {_16 return value * 2_16}_16_16function getPeopleNameF(people) {_16 return people.map((person) => person.name)_16}_16_16// Arrow function version_16const doubleValueA = (value) => value * 2_16_16const getPeopleNameA = (people) => people.map((person) => person.name)_16_16// Destructuring_16const 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:
_9const 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:
- If the person is under 18 years old they can't enter the party.
- If they are under 21 but 18 or older they can enter but not drink.
- If they are older then they can enter and drink.
We have a few issues in this code that we can correct:
- In line 4, we don't need to check if the person is older than 18 because we already returned them in line 2.
- We just need to return a message, so we can put the condition and return statement in one single line, some programmers don't like that way, so use what you think it's the best for you.
- We can replace the keywords else if and else for if statements and even omit the keyword in the last line.
Let's refactor and check it out.
_6const 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.
_6const 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:
_10const 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:
_6const UserCard = ({ name, age, job }) => <></>_6_6const App = () =>_6 users.map(({ name, age, job }) => (_6 <UserCard name={name} age={age} job={job} />_6 ))
Using spread operator:
_1const 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_6if (isSunny) chosenClothing = SummerClothing_6else chosenClothing = WinterClothing_6_6// short circuit_6const chosenClothing = isSunny ? SummerClothing : WinterClothing
_5..._5_5if (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
_17const 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
_20const 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
_7const 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
_2const 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:
_3import Brain from './Brain.png'_3import BriefCase from './BriefCase.png'_3import BuildingConstruction from './BuildingConstruction.png'
Now using an index file inside the images folder:
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:
_2if (user.roles.includes('ADMIN') || (user.age > 18 && user.reputation > 10))_2 return renderManagementPannel()
Let's refactor the code transforming the complicated conditionals into variables:
_4const isAdmin = user.roles.includes('ADMIN')_4const responsibleUser = user.age > 18 && user.reputation > 10_4_4if (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.