Photo by Lautaro Andreani on Unsplash
React JavaScript Concepts
Key JavaScript components to grasp before you dive into React
Over a couple of months, I have been dabbling with React JS tutorials on YouTube and Udemy. Halfway through the tutorials, the concepts used seemed more and more strange to the point where I felt discouraged and confused. On several occasions, I came across mentions of the importance of first grasping key JavaScript concepts to better understand and use React JS.
Map
One of the most often used methods is Array.map
()
, which allows you to iterate over an array and modify its elements using a callback function. The callback function will be run on each array element.
Assume we have an array of users that contains their information:
let users = [
{firstName: 'Susan', lastName: 'Steward', age: 14, hobby: 'Singing'},
{firstName: 'Daniel', lastName: 'Longbottom', age: 16, hobby: 'Football'},
{firstName: 'Jacob', lastName: 'Black', age: 15, hobby: 'Singing'}
]
We can loop through using map
and modify its output:
let singleUser = users.map(user => {
// Join first and second name together
let fullName = user.firstName + ' ' + user.lastName
return `
<h3 class='name'>${fullName}</h3>
<p class='age'>${user.age}</p>
`
})
You should note that:
map()
always returns a new array, even if it’s an empty array.map()
doesn’t change the size of the original array compared to thefilter
method.map()
always makes use of the values from your original array when making a new one.
Filter and find
Filter()
provides a new array depending on certain criteria. Unlike map()
, it can alter the size of the new array, whereas find()
returns just a single instance (this might be an object or item). If several matches exist, it returns the first match – otherwise, it returns undefined
.
Suppose you have an array collection of registered users with different ages:
let users = [
{ firstName: "Susan", age: 14 },
{ firstName: "Daniel", age: 16 },
{ firstName: "Bruno", age: 56 },
{ firstName: "Jacob", age: 15 },
{ firstName: "Sam", age: 64 },
{ firstName: "Dave", age: 56 },
{ firstName: "Neils", age: 65 },
]
You could choose to sort this data by age groups, such as young individuals (ages 1-15), senior people (ages 50-70), and so on.
In this case, the filter function comes in handy as it produces a new array based on the criteria. Let's have a look at how it works:
// For young people
const youngPeople = users.filter(user => {
return user.age <= 15
})
// For senior people
const seniorPeople = users.filter(user => user.age >= 50 )
This generates a new array. It produces an empty array if the condition is not satisfied(no match).
The find()
method, like the filter() method, iterates across the array looking for an instance/item that meets the specified condition. Once it finds it, it returns that specific array item and immediately terminates the loop. If no match is discovered, the function returns undefined. For example:
const Bruno = users.find(user => user.firstName === "Bruno")
Reduce
This is arguably the most powerful array function. It can replace the filter() and find() methods and is also quite handy when doing map() and filter() methods on large amounts of data.
When you chain map and filter method together, you wind up doing the work twice – first filtering every single value and then mapping the remaining values. On the other hand, reduce() allows you to filter and map in a single pass. This method is powerful, but it's also a little more sophisticated.
We iterate over our array and then obtain a callback function. This reduces our array to a single value, which may be a number, array, or object.
Another thing to keep in mind about the reduce() method is that we are passing in two arguments, which has not been the case since you began reading this tutorial.
The first argument is the sum/total of all computations, and the second is the current iteration value. For example, suppose we have a list of salaries for our staff:
let staffs = [
{ name: "Susan", age: 14, salary: 100 },
{ name: "Daniel", age: 16, salary: 120 },
{ name: "Bruno", age: 56, salary: 400 },
{ name: "Jacob", age: 15, salary: 110 },
{ name: "Sam", age: 64, salary: 500 },
{ name: "Dave", age: 56, salary: 380 },
{ name: "Neils", age: 65, salary: 540 }
]
And we want to calculate a 10% tithe for all staff. We could easily do this with the reduce method, but before doing that let's do something easier: let’s calculate the total salary first.
const totalSalary = staffs.reduce((total, staff) => total += staff.salary, 0)
Note that we passed a second argument which is the total, it could be anything – for example a number or object.
Let’s now calculate the 10% tithe for all staff and get the total. We could just get the 10% from the total or first get it from each salary before adding them up.
const salaryInfo = staffs.reduce((total, staff) => {
let staffTithe = staff.salary * 0.1
total.totalTithe += staffTithe
total['totalSalary'] += staff.salary
return total
}, { totalSalary: 0, totalTithe: 0 })
Gotcha: We used an object as the second argument and we also used dynamic object keys.
Dynamic Object Keys
This enables us to add object keys using square bracket notation. In JavaScript, we know that objects are often made up of properties/keys and values, and we may use the dot notation to add, edit, or access some value(s). As an example:
let lion = {
category: "carnivore"
}
console.log(lion)
lion.baby = "cub"
console.log(lion.category)
console.log(lion)
We also have the option of using square bracket notation, which is utilized when we need dynamic object keys. These are keys that might not follow the standard naming convention of properties/keys in an object. The standard naming convention only permits camelCase
and snake_case
, but by using square bracket notation we can solve this problem.
For example, suppose we name our key with a dash in between words, for example (lion-baby):
lion = {
'lion-baby': "cub"
}
// dot notation
console.log(lion.lion-baby) // ReferenceError: baby is not defined
// bracket notation
console.log(lion['lion-baby'])
You can see the difference between the dot notation and the bracket notation. Let's see other examples:
let category = 'carnivore'
lion = {
'lion-baby': "cub",
[category]: true
}
You can also perform more complex operations by using conditions within the square bracket, like this:
const number = 5
const gaveBirth = true
let animal = {
name: 'lion',
age: 6,
[gaveBirth && 'babies']: number
}
Callback Functions
A callback function is a function that is performed after another function has completed its execution. It is typically supplied as an input into another function.
const btn = document.querySelector(".btn")
btn.addEventListener('click', () => {
let name = 'John Doe'
console.log(name.toUpperCase())
})
Destructuring and Array Objects
Destructuring is a JavaScript feature introduced in ES6 that allows for faster and simpler access to and unpacking of variables from arrays and objects. Before destructuring was introduced, if we had an array of fruits and wanted to get the first, second, and third fruits separately, we would end up with something like this:
let fruits = ["Mango", "Pineapple" , "Orange", "Lemon", "Apple"]
let fruit1 = fruits[0]
let fruit2 = fruits[1]
let fruit3 = fruits[2]
...
This is repeating the same thing over and over which could become cumbersome. Let's see how this could be destructured to get the first 3 fruits:
let [fruits1, fruits2, fruits3] = fruits
You might be wondering how you could skip data if you just want to print the first and final fruits, or the second and fourth fruits. You would use commas as follows:
const [first ,,,, fifth] = fruits
const [,second,, fourth,] = fruits
Object Destructuring
Let’s now see how we could destructure an object – because in React you will be doing a lot of object destructuring. Suppose we have a user object that contains the user's first name, last name, and additional attributes:
const Susan = {
firstName: "Susan",
lastName: "Steward",
age: 14,
hobbies: {
hobby1: "singing",
hobby2: "dancing"
}
}
In the old way, getting these data could be stressful and full of repetition:
const firstName = Susan.firstName
const age = Susan.age
const hobby1 = Susan.hobbies.hobby1
but with destructuring its a lot easier:
const {firstName, age, hobbies:{hobby1}} = Susan
We can also do this within a function:
function individualData({firstName, age, hobbies: {hobby2}}) {
console.log(firstName, age, hobby2)
}
individualData(Susan)
Rest and Spread Operators
JavaScript spread and rest operators use three dots (...). The rest operator collects items and puts the “rest” of some specific user-supplied values into a JavaScript array/object. Suppose you have an array of fruits:
let fruits = ["Mango", "Pineapple" , "Orange", "Lemon", "Apple"]
We could destructure to get the first and second fruits and then place the“rest” of the fruits in an array by making use of the rest operator.
const [first, second, ...rest] = fruits
console.log(first, second, rest) // "Mango", "Pineapple", ["Orange","Lemon","Apple"]
Looking at the result, you'll see the first two items and then the third item is an array consisting of the remaining fruits that we didn't destructure. We can now conduct any type of processing on the newly generated array, such as:
const chosenFruit = rest.find(fruit => fruit === "Apple")
It is important to bear in mind that this has to come last always (placement is very important).
We've just worked with arrays, now let's deal with objects, which are similar. Assume we had a user object that has their first name, last name, and others. We could destructure it and then extract the remainder of the data.
const Susan = { firstName: "Susan", lastName: "Steward", age: 14, hobbies: { hobby1: "singing", hobby2: "dancing" } }
const {age, ...rest} = Susan console.log(age, rest)
The spread operator, as the name implies, is used to spread out array items. It gives us the ability to get a list of parameters from an array. A spread operator is effective only when used within array literals, function calls, or initialized properties objects.
For example, suppose you have arrays of different types of animals:
let pets= ["cat", "dog" , "rabbits"]
let carnivorous = ["lion", "wolf", "leopard", "tiger"]
You might want to combine these two arrays into just one animal array. We can achieve this using the spread operator:
let animals = [...pets, ...carnivorous]
This also works with objects. It is important to note that the spread operator cannot expand the values of object literals, since a properties object is not an iterable. But we can use it to clone properties from one object into another. Example:
let name = {first: "John", last: "Doe"}
let hobbies = {hobby1: "singing", hobby2: "dancing"}
let myInfo = {...name, ...hobbies}
Fetch API and Errors
The fetch API, as the name implies, is used to get data from APIs. It is a browser API that allows you to use JavaScript to make basic AJAX (Asynchronous JavaScript and XML) requests.
Because it is given by the browser, you may use it without having to install or import any packages or dependencies. Its configuration is fairly simple. The fetch API delivers a promise by default.
Let’s see how to fetch data via the fetch API. We'll use a free API which contains thousands of random quotes:
fetch("https://type.fit/api/quotes")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((err) => console.log(err))
What we did here was:
We got the data from the API, which returned a promise
We then got the .json() format of the data which is also a promise
We got our data which now returns JSON
We got the errors in case there are any
We will see how this can be done with async/await in the next section.
Let’s now take a look at how we can handle errors from fetch API without needing to depend on the catch keyword. The fetch() function will automatically throw an error for network errors but not for HTTP errors such as 400 to 5xx responses.
The good news is fetch provides a simple response.ok
flag that indicates whether the request failed or an HTTP response’s status code is in the successful range.
fetch("https://type.fit/api/quotes")
.then((response) => {
if(!response.ok) {
throw Error(response.statusText)
}
return response.json()
})
.then((data) => console.log(data))
.catch((err) => console.log(err))
Async/Await
Async/await allows us to write asynchronous code synchronously. This means that you don't need to continue nesting callbacks. An async function always returns a promise.
Synchronous means that jobs are completed one after the other. Asynchronous means that tasks are completed independently.
We always have async in front of the function and we can only use await when we have async.
Let’s now implement the Fetch API code we worked on earlier using async/await:
const fetchData = async () => {
const quotes = await fetch("https://type.fit/api/quotes")
const response = await quotes.json()
console.log(response)
}
fetchData()
This is way easier to read.
You might be wondering how we can handle errors with async/await. You use the try-and-catch keywords:
const fetchData2 = async () => {
try {
const quotes = await fetch("https://type.fit/api/quotes")
const response = await quotes.json()
console.log(response)
}
catch (error) {
console.log(error)
}
}
fetchData2()