JavaScript for React
It's important to have a basic understanding of JavaScript before digging into React. Not all JavaScript concepts and patterns but the foundations.
Declaring variables
Before ES6, the only way to declare a variable was with the var
keyword,
but now we have more options:
The const
keyword
The value of a constant can't be changed through reassignment (i.e., by
using the assignment operator =
), and it can't be redeclared.
const odd = true
odd = false // Error: Assignment to constant variable
const odd = 123 /// Error: `odd` has already been declared
However, if a constant is an object or array its properties or items can be updated or removed.
// Arrays
const num = [1, 2, 3]
num.push(4)
console.log(num) // [1, 2, 3, 4]
// Objects
const person = { name: 'Hamed' }
person.lastName = 'Bahram'
console.log(person) // { name: 'Hamed', lastName: 'Bahram'}
The let
keyword
The let
keyword declares a variable, that can be reassigned but not
redeclared.
let even = true
even = false // no error
let even = 123 // Error: `even` has already been declared
Both const
and let
are block-scoped. Blocks are created using curly
braces {}
, and these curly braces block off the scope of any variable
declared inside of them.
let x = 1
if (x === 1) {
let x = 2
console.log(x) // expected output: 2
}
console.log(x) // expected output: 1
The variable x
inside curly braces (the if block) is scoped to that
block, therefore not affecting the global variable x
outside.
Template literals
Template literals are delimited with the backtick ( ` ) character allowing multi-line strings and string interpolation with embedded expressions.
Template literals are sometimes referred to as template strings, because
they are used for string interpolation. Traditional string concatenation
uses plus signs +
to compose a string.
const name = 'Hamed'
const greeting = 'Hello' + ' ' + name
console.log(greeting) // 'Hello Hamed'
With a template literal we can create a string and insert the variable by
wrapping it inside ${}
.
const name = 'Hamed'
const greeting = `Hello ${name}`
You can also have a string that spans over multiple lines without breaking your code:
const message = `
Welcome ${name},
Thank you for reading my posts.
Cheers!
`
Functions
Anytime you find yourself repeating a task, you can define a function that performs that task, and then call the function instead of repeating the code, when needed.
Defining functions
Function declarations
A function declaration, consist of a function
keyword followed by the
name of the function, a list of parameters, enclosed in parentheses, and
the JavaScript statements that define the task, enclosed in curly braces.
function sum(a, b) {
return a + b
}
Once you declared the function, you can call the function to execute the code:
sum(2, 2) // Expected output: 4
The return
statement specifies the value returned by the function.
return a + b
Default parameters
You can specify a default value for each of your function parameters to be used in the event that a value is not provided when the function is called
function greet(name = 'friend') {
return `Hey ${name}!`
}
greet('Alex') // Expected output: Hey Alex!
greet() // Expected output: Hey friends!
Function expressions
A function expression involves creating the function as a variable.
const sum = function (a, b) {
return a + b
}
sum(2, 2) // Expected output: 4
One difference between function declarations and function expressions is that function declarations are hoisted, meaning you can call them before your define them, whereas function expressions are not.
// Calling the function before it's declared, works here.
hi()
function hi() {
console.log('hi')
}
// Calling the function expression before it's declared results in Error
bye()
const bye = function () {
console.log('bye')
}
Arrow functions
With arrow functions you can create functions without using the function
keyword. This is an alternative to the traditional function expression but
with some differences.
// function expression
const sum = function (a, b) {
return a + b
}
// arrow function
const sum = (a, b) => {
return a + b
}
If your function is simple and consists of only one statement you can
remove the curly braces and the return
keyword, making the syntax more
concise:
// concise arrow function
const sum = (a, b) => a + b
You can also remove the parentheses if the function has only one argument:
// no parenthesis
const square = num => num * num
The main difference between arrow functions and traditional functions, is
that in traditional functions, the value of this
is determined by how a
function is called (the execution context). While arrow functions don't
have their own this
binding and retain the value of the enclosing lexical
context, regardless of how they are called.
const person = {
name: 'Hamed',
printName: function () {
console.log(this.name)
}
}
// `this` inside printName refers to the person object
person.printName()
const person = {
name: 'Hamed',
printName: () => {
console.log(this.name)
}
}
// `this` inside printName refers to the global object
person.printName()
As you can see above arrow function are not a good choice for object methods.
Returning Objects
When returning an object from a concise arrow function you can wrap the object in parenthesis to prevent the object's curly braces from being considered as the function body:
// regular arrow function with `return` keyword
const user = (name, email) => {
return {
name: name,
email: email
}
}
// concise arrow function with implicit return
const user = (name, email) => ({
name: name,
email: email
})
You can also use the ES6 object literal shorthand syntax for an even shorter syntax:
// `user` is a function that returns an object
// with `name` and `email` properties
const user = (name, email) => ({ name, email })
Objects and Arrays
Let's look at a few of the Javascript ES2016 syntax, including destructuring, object literal shorthand, and the spread operator.
Object destructuring
Destructuring assignment allows you to pull fields out of the object and create local variables from them.
const person = {
firstName: 'John',
lastName: 'Doe',
gender: 'male',
age: '26'
}
const { firstName, lastName } = person
console.log(firstName, lastName) // Output: John Doe
We can also destructure objects passed to functions as an argument:
const person = {
firstName: 'John',
lastName: 'Doe',
gender: 'male',
age: '26'
}
// Using dot notation to get the firstName
const printName = personObject => {
console.log(personObject.firstName)
}
// Destructuring the argument
const printName = ({ firstName }) => {
console.log(firstName)
}
printName(person) // Output: John
We can also destructure a nested object:
const person = {
firstName: 'John',
lastName: 'Doe',
gender: 'male',
age: '26',
spouse: {
firstName: 'Alicia',
lastName: 'keys'
}
}
const printSpouseName = ({ spouse: { firstName } }) => {
console.log(firstName)
}
printSpouseName(person) // Output: Alicia
By using the :
and the nested curly braces, we were able to destructure
the firstName
from the spouse
object.
Array destructuring
Values can also be destructured from arrays.
const [first] = ['red', 'green', 'blue']
console.log(first) // Output: red
We can also skip values using commas ,
. Commas replace the elements that
should be skipped.
const [, , third] = ['red', 'green', 'blue']
console.log(third) // Output: blue
You can end a destructuring pattern with a rest property ...rest
. This
pattern will store all remaining properties of the object or array into a
new object or array.
const seasons = ['spring', 'summer', 'fall', 'winter']
const [first, ...remaining] = seasons
console.log(first) // output: 'spring'
console.log(remaining) // output: ['summer', 'fall', 'winter']
Object literal shorthand
Object literal shorthand is the opposite of destructuring, it's putting the object together. It allows us to pull variables into an object with a shorthand syntax:
const firstName = 'Alicia'
const lastName = 'Keys'
const printName = function () {
console.log(this.firstName)
}
const person = { firstName, lastName, printName }
console.log(person) // output: { firstName: 'Alicia', lastName: 'Keys' }
Also when defining object methods, we can eliminate the colon :
and the
function
keyword:
// Old way
const pet = {
name: 'bailey',
bark: function () {
console.log('woof')
}
}
// New way
const pet = {
name: 'bailey',
bark() {
console.log('woof')
}
}
The spread operator
The spread operator expands an array when used in array literals. We can use it to easily copy arrays:
const first = [1, 2, 3]
const second = [4, 5, 6]
const both = [...first, ...second]
console.log(both) // Output: [1, 2, 3, 4, 5, 6]
The spread operator expands an array when used in function calls where zero or more arguments are expected:
const numbers = [1, 2, 3]
function sum(x, y, z) {
return x + y + z
}
sum(...numbers) // Output: 6
In an object literal, the spread syntax adds the key-value pairs to the object being created.
const father = {
firstName: 'John',
lastName: 'Barrymore'
}
const daughter = { ...father, firstName: 'Drew' }
// output: { firstName: 'Drew', lastName: 'Barrymore'}
The rest operator
The rest operator looks exactly like the spread operator, but works the opposite way. While the Spread syntax expands an array into its elements, the rest operator collects multiple elements into an array.
function sum(...args) {
// `args` will be an array of passed arguments
const total = args.reduce((acc, curr) => (acc += curr), 0)
return total
}
sum(1, 2, 3) // output: 6
sum(1, 2, 3, 4) // output: 10
The sum
function takes in the arguments using the rest operator ...args
which condenses all of the passed arguments into an array. This allows the
function to accept and handle any number of arguments.
Asynchronous JavaScript
Tasks that take time to complete like accessing a database, or fetching data from an API, when handled asynchronously, do not block the main thread, leaving your program responsive to other events and free to do something else while they complete.
Let's explore some of the JavaScript features that make this possible:
Promise
A promise is an object that represents the state of an asynchronous task and its resulting value.
A Promise is in one of these states:
- pending: initial state, neither fulfilled nor rejected.
- fulfilled: meaning that the operation was completed successfully.
- rejected: meaning that the operation failed.
The final state of a pending promise can either be fulfilled with a value
or rejected with an error. When either of these happen, the associated
callbacks defined by a promise's then
methods are called.
myPromise
.then(handleFulfilledA, handleRejectedA)
.then(handleFulfilledB, handleRejectedB)
.then(handleFulfilledC, handleRejectedC)
The methods then
, catch
, and finally
are used to associate further
action with a promise that becomes settled.
The then
method takes up to two arguments; the first argument is a
callback function for the fulfilled case of the promise, and the second
argument is a callback function for the rejected case. Each then
returns
a new promise, which can optionally be chained.
A chain can safely omit the rejection callback functions until the final
catch
. A catch
method is really just a then
method without a slot for
a callback function for the case when the promise is fulfilled.
myPromise
.then(handleFulfilledA)
.then(handleFulfilledB)
.then(handleFulfilledC)
.catch(handleRejectedAny)
The return value of each fulfilled promise is passed along to the next
then
, while the reason for rejection (i.e. the error) is passed to the
next rejection-handler function in the chain.
myPromise
.then(value => /* do something */)
.then(value => /* do something */)
.catch(error => /* handle the error */)
The finally
method of a Promise schedules a callback, to be called when
the promise is settled (i.e. fulfilled or rejected). The finally
method can be useful if you want to do something once the promise is
settled, regardless of its success or failure. This lets you avoid
duplicating code in the then
and catch
handlers.
Fetch API
The Fetch API simplifies making a request to fetch a resource from the
network. The fetch
method takes the path to the resource you want to
fetch, and returns a Promise which is fulfilled once the response is
available.
fetch(resource, options)
options
is an optional object containing settings you want to apply to
the request being sent. For more details, you can refer to the
MDN.
The Promise returned from fetch
won't reject on HTTP error status even if
the response is an HTTP 404 or 500. Instead, it will resolve normally with
ok
status set to false
, and it will only reject on network failure or
if anything prevented the request from completing.
Async/Await
Work in progress...