10 Things to Know About ES6 before Learning React JS
When you start learning React JS, you’ll probably feel lost if you’re not familiar with the ES6 syntax, even if you have previous experience with Javascript.
If you don’t know much about ES6, ES6 is a standard implemented by Javascript, and it describes all the rules, details, and guidelines that a Javascript implementation should have. And I’ll tell you a secret, there are many different Javascript implementations out there, and that’s why some ES6 features are not available in some browsers (I’m looking at you, IE).
React JS fully embraces ES6, so once you learn and understand the ES6 syntax, your life as a React developer will change because it will be a lot easier to read and write React code.
In this tutorial I will show you some of the ES6 features I think are important to know before you start learning React. But if you prefer to learn from videos like me, check out this video on YouTube:
Are you still here? If so, let’s get started!
Instead of memorizing fancy concepts it’s always easier to understand them by checking examples. Also if possible, I would recommend trying the examples yourself.
const, let, and var
Before ES6, we only had one way to declare variables in Javascript: the “var” keyword. But now we also have “const” and “let”.
const
Variables defined with “const” can’t be redeclared or reassigned.
If you try to reassign a variable defined with “const”, you will get a syntax error:
1 2 3 4 5 6 7 8 | // Reassign const const id = 1; id = 2; // Error |
But if the variable is an object or array, then you can actually change the values of the properties/elements they hold:
1 2 3 4 5 6 7 8 9 10 11 12 13 | const user = { id: 1, name: 'Carlos', email: 'carlos@gmail.com', }; // Assign value user.email = 'carlos@hotmail.com'; // No error |
Similarly with arrays:
1 2 3 4 5 6 7 8 9 10 11 | const ids = [1, 2, 3, 4, 5]; ids[4] = 6; // No error console.log('ids', ids); // output: [1, 2, 3, 4, 6] |
But you can’t reassign the whole object or array:
1 2 3 4 5 6 7 8 9 | const ids = [1, 2, 3, 4, 5]; // Reassign array ids = [1, 2, 3, 4, 6]; // Error |
Variables declared with “const” are block scoped, that means they can only be accessed within the block they were declared. The scope of a variable means where you can access the variable within your code. Let’s see an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 | const name = 'Carlos'; function getName(){ console.log(name); // undefined } console.log(name); // output: Carlos |
If you try to access the variable from the function, you will get and undefined error, but outside the function, it will work.
let
A variable declared with “let” can be reassigned:
1 2 3 4 5 6 7 8 | let name = 'Carlos'; name = 'Brad'; // No error |
But can’t be redeclared:
1 2 3 4 5 6 7 | let name = 'Carlos'; let name = 'Brad'; // Error |
Similarly to “const”, variables declared with “let” are block scoped, that means they can only be accessed within the block they were declared. For example, if you use a “let” variable in a for loop, you can’t access that variable outside the for loop :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | for (let i = 0; i < 3; i++) { console.log(i); } // output: // 0 // 1 // 2 console.log(i); // output: undefined |
var
The “var” keyword is what we all have been using for many years in Javascript, but one of the main issues of declaring variables with “var”, is the scope. If you declare any variable outside a function with “var”, it will be globally scoped. This means you can access that variable from other functions and blocks. This makes your code prone to bugs and hard to debug code. Especially if you run multiple scripts on one page. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | for (var i = 0; i < 3; i++) { console.log(i); } // output: // 0 // 1 // 2 console.log(i); // output: 3 |
You can also redeclare “var” defined variables:
1 2 3 4 5 6 7 | var id = 1; var id = 2; // No error |
Also reassign them:
1 2 3 4 5 6 7 | var id = 1; id = 2; // No error |
When to use what?
You may find many different opinions between developers on how to implement these keywords to define variables. But this is what I would suggest:
- “const” should be your first option when you define a variable now in Javascript unless you know the value will change.
- You should only use “let” if you know the value of the variable will change later in your code.
- “var” should not be an option anymore, and you’ll rarely see the use of “var” in React.
Arrow functions
Arrow functions are a big part of React JS; you will use them everywhere.
This is how you usually create a function before ES6:
1 2 3 4 5 6 7 | const sum = function(a, b) { return a + b; }; |
And this is how the same function would look like as an Arrow function:
1 2 3 4 5 6 7 | const sum = (a, b) => { return a + b; }; |
You can notice there’s no “function” keyword. Also you have this arrow (=>) next to the parameters.
But if we only have one line of code in the function, like in this case, we could remove the “return” keyword because there’s an implicit return and also the curly braces. It will look a lot cleaner now:
1 2 3 4 5 6 7 | const sum = (a, b) => a + b; console.log(sum(1, 2)); // output: 3 |
Also with Arrow functions, we could define default values for our parameters:
1 2 3 4 5 6 7 8 9 | // Default value const sum = (a, b = 0) => a + b; console.log(sum(1)); //output: 1 |
Destructuring assignment Arrays and Objects
Destructuring assignment allows you to extract individual items from arrays or objects and assign them to variables using a shorthand syntax.
Destructuring Arrays
Let’s see an example with Arrays. We usually do this to extract values from an array and assign them to variables:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var os = ['linux', 'mac', 'windows']; var os1 = os[0]; var os2 = os[1]; var os3 = os[2]; console.log('os1:', os1); console.log('os2:', os2); console.log('os3:', os3); //output: // os1:linux // os2:mac // os3:windows |
But using ES6 destructuring it becomes a lot simpler:
1 2 3 4 5 6 7 8 9 10 11 12 13 | const [os1, os2, os3] = os; console.log('os1:', os1); console.log('os2:', os2); console.log('os3:', os3); //output: // os1:linux // os2:mac // os3:windows |
If you come from PHP, this is very similar to what the list() function does.
Destructuring Objects
Now let’s see and example with objects. Instead of using brackets [] like in the arrays, we enclose the variables with curly braces {} and the properties of the object are assigned to variables of the same name:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Object const user = { id: 1, name: 'Carlos', email: 'carlos@gmail.com', }; const {id, name, email} = user; console.log(id); console.log(name); console.log(email); //output: // 1 // Carlos // carlos@gmail.com |
But we could also change the name of the variables:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // Object const user = { id: 1, name: 'Carlos', email: 'carlos@gmail.com', }; const {id: ID, name: firstname, email: email_address} = user; console.log(ID); console.log(firstname); console.log(email_address); //output: // 1 // Carlos // carlos@gmail.com |
Spread Operator
The spread operator allows you to copy arrays and objects in a shorthand syntax.
Spread Operator for Arrays
Let’s see it in action with arrays:
1 2 3 4 5 6 7 8 9 10 11 12 13 | const numbers1 = [1, 2, 3, 4]; const numbers2 = [...numbers1]; console.log('numbers1:', numbers1); console.log('numbers2:', numbers2); //output: // numbers1:[1, 2, 3, 4] // numbers2:[1, 2, 3, 4] |
In that example, we copy the values from numbers1 into numbers2. By adding “…” as prefix to the array, you spread it out.
We could also add more items to the array:
1 2 3 4 5 6 7 8 9 10 11 12 13 | const numbers1 = [1, 2, 3, 4]; const numbers2 = [...numbers1, 5]; console.log('numbers1:', numbers1); console.log('numbers2:', numbers2); //output: // numbers1:[1, 2, 3, 4] // numbers2:[1, 2, 3, 4, 5] |
The Spread Operator also can be useful when you have a variable that is an array, and you want to pass it to a function that takes an unknown number of arguments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | const numbers = [1, 2, 3]; // Not allowed const total = Math.max(numbers); console.log(total); // Pre-ES6 const total = Math.max(numbers[0], numbers[1], numbers[2]); console.log(total); //output: 3 // ES6 const total = Math.max(...numbers); console.log(total); //output: 3 |
In the first example, you won’t be able to call the function just passing the numbers variable as an argument. You need to pass them individually or to add the “…” as prefix to the array to spread the values out.
And here is another cool trick, you could also apply the Spread Operator to a string to convert it to an array:
1 2 3 4 5 6 7 8 9 10 | const name = 'Carlos'; const nameArray = [...name]; console.log('nameArray:', nameArray); //output: nameArray:['C','a', 'r', 'l', 'o', 's'] |
Spread Operator for Objects
Now let’s see some examples with objects:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | const user = { id: 1, name: 'Carlos', email: 'carlos@gmail.com', }; const user2 = { ...user}; console.log('user:', user); console.log('user2:', user2); //output: // user: {id: 1, name: "Carlos", email: "carlos@gmail.com"} // user2: {id: 1, name: "Carlos", email: "carlos@gmail.com"} |
In that example, similarly to arrays, we can copy an object by adding the “…” prefix and assigning it to another variable.
The spread operator is also very useful to merge objects. This is a very common if you use something like redux to manage your state in React:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | const user2 = { ...user, email: 'carlos@hotmail.com' }; console.log('user', user); console.log('user2', user2); // output: // user: {id: 1, name: "Carlos", email: "carlos@gmail.com"} // user2:{id: 1, name: "Carlos", email: "carlos@hotmail.com"} |
Rest Parameters
In some cases you need to create functions that accept an unknown number of parameters. Before ES6, you could do this using the “arguments” keyword. Let’s see an example:
1 2 3 4 5 6 7 8 9 10 11 12 | const sum = function(a, b) { return a + b; }; let total = sum(1, 2); console.log('total:', total); //output: total:3 |
In that example, we have a sum() function that takes two parameters, but what if we wanted to pass more than two arguments (parameters) to the function?:
1 2 3 4 5 6 7 8 | let total = sum(1, 2, 3, 4); console.log('total:', total); //output: total:3 |
We would get the same result, and Javascript will ignore the other arguments.
Javascript provides the “arguments” object to access all the arguments passed to the function. If we console.log “arguments” we could see it’s an object:
1 2 3 4 5 6 7 8 9 10 11 12 | const sum = function(a, b) { console.log(arguments); return a + b; }; sum(1,2); // output: // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ] |
The problem with “arguments” is that we can’t apply array functions to it because it’s an object, so we would need to implement other methods to do our calculations. That’s why ES6 introduced Rest Parameters, if we add “…” as a prefix to the parameter in the function definition, then it becomes an array of arguments when the function is called. Then we could apply any array function to it or just have the ability to loop it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | const sum = function(...args) { let total = 0; for (let arg of args) { total += arg; } return total; }; let total = sum(1, 2, 3, 4, 5, 6); console.log('total:', total); //output: total:21 |
The syntax for Rest Parameters is similar to the Spread Operator, it uses the “…” but you could differentiate them by checking where they are used. If you see “…” in the parameters of the function definition, then you are seeing Rest Parameters. Otherwise it is a Spread Operator.
Template Literals
At some point, we all have to concatenate (join) variables with strings in Javascript, for example, showing a personalized message to the user or customizing a button label. And we know this is a pain to do in Javascript because you have to keep adding plus operators (+) and quotes for every string you want to concatenate.
In React we use JSX (JSX is syntax used in React JS that looks like HTML), so we won’t be using template literals that much because we can easily include variables and expressions in the tags, but I think it’s an important feature.
Let’s see an example:
1 2 3 4 5 6 7 8 9 10 11 | let firstname = 'Carlos'; // Concatenating with + let hello = 'Hi my name is ' + firstname; console.log(hello); //output: Hi my name is Carlos |
And if we have to add more strings or the strings include other characters like quotes, they need to be escaped with “\” then the code becomes more harder to read and error prone:
1 2 3 4 5 6 7 8 | let hello = 'Hi my name is ' + firstname + ' and my friends call me \'gigo\'' ; console.log(hello); //output: Hi my name is Carlos and my friends call me 'gigo' |
If we use template literals, then it will be a lot simpler. We need to add a backtick “`” (the key below the ESC key) to the beginning and end of the string and use “${}” for the variables:
1 2 3 4 5 6 7 8 9 | // ES6 - backtick character let hello = `Hi my name is ${firstname} and my friends call me 'gigo'`; console.log(hello); //output: Hi my name is Carlos and my friends call me 'gigo' |
You can also have multiline strings:
1 2 3 4 5 6 7 8 9 10 | // Return - multiline strings let hello = `Hi my name is: ${firstname}`; // output: Hi my name is: // Carlos |
Map
The map() method is used to apply a function to every element in an array and return a new array.
Let’s say we have an array of user objects:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | const users = [ { id: 1, name: 'Carlos', email: 'carlos@gmail.com', deleted: true, }, { id: 2, name: 'John', email: 'john@gmail.com', deleted: true, }, { id: 3, name: 'Brad', email: 'brad@gmail.com', deleted: false, }, ]; |
If we wanted to get the IDs of this users and assign them to a new array, we would do this:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Pre-ES6 var userIds = []; for (var i = 0; i < users.length; i++) { userIds.push(users[i].id); } console.log('userIds:', userIds); //output: userIds:[1, 2, 3] |
But let’s see how we would do it in ES6 using map(). It would take us just one line of code:
1 2 3 4 5 6 7 8 9 10 | // ES6 userIds = users.map(user => user.id); console.log('userIds', userIds); //output: userIds:[1, 2, 3] |
map() is frequently use in React JSX to render list tags from an array, let’s see an example simulating JSX but as just a string:
1 2 3 4 5 6 7 8 9 10 11 12 | const liItems = users.map(user => `<li id="${user.id}">${user.name}</li>`); console.log('liItems:', liItems); //output: liItems: 0:<li id="1">Carlos</li> 1:<li id="2">John</li> 2:<li id="3">Brad</li> |
Filter (ES5)
The filter() method was introduced in ES5 not ES6, but I wanted to include it in this tutorial because you will see it everywhere in React code.
It creates a new array with all elements that pass the condition implemented by the provided function. In other words, if the function you pass to filter() returns “true”, then it will go to the new array.
If for example we wanted to get all the users that were deleted ( deleted = true) from the same array of users, we would do this:
1 2 3 4 5 6 7 8 9 10 11 12 | // ES6 const deletedUsers = users.filter(user => user.deleted); console.log('deletedUsers:', deletedUsers); // output: deletedUsers: //0:{id: 1, name: "Carlos", email: "carlos@gmail.com", deleted: true} //1:{id: 2, name: "John", email: "john@gmail.com", deleted: true} |
Classes/Subclasses
Classes
For many years, Javascript developers have been emulating classes with functions, but classes are now finally included in Javascript with ES6.
Classes have been an important part of many languages. If you are familiar with other languages that support classes, then you should have no problem using classes in Javascript.
In React it’s common to have class-based components so that’s why it’s very important to understand how ES6 classes work and their syntax. A class is a template of an object. Object-oriented Programming (OOP) is a huge topic, so I will just show you here some very basic examples in Javascript.
To define a class, we use the “class” keyword and give it a name, which by convention should have the first letter capitalized:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class User { constructor(id, name) { this.id = id; this.name = name; } const user = new User(1, 'Carlos'); console.log('user:', user); //output: user: // User {id: 1, name: "Carlos"} |
Now let’s add some methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class User { constructor(id, name) { this.id = id; this.name = name; } login() { return `${this.name} is logged in`; } } const user = new User(1, 'Carlos'); console.log('user.login():', user.login()); //output: user.login(): Carlos is logged in |
Subclasses
We can also have subclasses. Subclasses extend another class, so the subclass inherits the properties and methods of the original class.
You will see this a lot in React JS when you want to create a new Component:
1 2 3 4 5 6 7 | class App extends Component{ } |
Let’s create a new subclass called Student that extends from User.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Student extends User { constructor(id, name) { This.id = id; This.name = name; } } const student = new Student(1, 'Carlos'); // Error |
But we will get an error if we do this because the subclass needs to always call super() in the subclass constructor:
1 2 3 4 5 6 7 8 9 | class Student extends User { constructor(id, name) { super(); } } |
By calling super(), we will call the parent class constructor method. This will allow us to use the “this” keyword in our subclass, and we could also add more properties and methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Student extends User { constructor(id, name) { super(); this.room = 1; } enroll() { return`${this.name} is enrolled`; } } const student = new Student(1, 'Carlos'); console.log('student.enroll():', student.enroll()); //output: student.enroll(): Carlos is enrolled |
Modules
For years Javascript have been trying to implement different solutions for modules using third-party libraries. But now ES6 brings modules into Javascript natively. Modules provide a way to include functionality from one file into another. As a good practice, code should be split into smaller files.
Let’s say we have a file called math.js, and in that file we have a function called sum():
1 2 3 4 5 6 7 8 | //math.js export function sum(...args){ // code here ... } |
By using the “export” keyword, we could import that function (module) in another file. In this case, we have a file called app.js, and we use the “import” keyword followed by the module name in curly braces and then the “from” keyword to specify the location of the file. If the file is in the same folder, use “./” as a prefix:
1 2 3 4 5 6 7 8 | // app.js import {sum} from './math.js'; sum(1,2); |
A file can have multiple exports, but we can also specify a default export if we use the default keyword:
1 2 3 4 5 6 7 8 | //math.js export default function sum(...args){ // code here ... } |
Now that we have a default export, we can import it without using the curly braces around the module name:
1 2 3 4 5 6 | // app.js import sum from './math.js'; |