10 JavaScript Concepts You Should Learn to Master React

10 JavaScript Concepts You Should Learn to Master React

Knowledge of JavaScript / ES6+ is important if you want to build React applications. Indeed, ES6+ brings a lot of cool stuff to JavaScript that makes writing React components much easier and cleaner.

While ES6 and its following updates came with many new features, there are a couple of concepts that you really need to know in order to write better and cleaner React apps. Mastering those concepts will make you a better JavaScript developer and brings your React applications to the next level.

Hence, I've decided to create this post in order to share with you the 10 most useful JavaScript / ES6+ concepts that you need to master to become a better React developer.

🔖Bookmark this post and share it with your developer friends! I hope you will enjoy it.

In case you want to learn more about ReactJS and improve your Javascript skills, have a look at my ReactJS online course here. No prior experience in ReactJS needed. You will learn how to build a real-world application step-by-step using modern ReactJS.

promo.png

Table of Contents

  1. Arrow Functions
  2. Default Parameters
  3. Template Literals
  4. Let and Const
  5. Classes
  6. Destructuring
  7. Ternary Operator
  8. Import / Export Module
  9. Async / Await
  10. Spread Operator / Rest Parameter

Arrow Functions

As you may know, the simplest way to define a React component is to write a JavaScript function like in the following example.

function MyComponent(props) {
  return <h1>Hello from AlterClass.io</h1>;
}

But there’s another very simple and concise way for creating React function components, that’s even better than regular functions. It’s called arrow functions.

const MyComponent = (props) => <h1>Hello from AlterClass.io</h1>;

As you can see, it allows us to write less code to achieve the same result.

Arrow functions are what you’ll see the most in JavaScript and React applications. So, it is a good idea to understand and master them.

Before diving into how they are used in React, let's see how to write them. Indeed, there are a variety of syntaxes available to write an arrow function. We’ll cover the common ones here to get you up and running.

// Basic syntax with multiple parameters
const add = (a, b) => { return a + b };

// Curly brackets aren’t required if only one expression is present
// The `return` keyword is also implicit and can be ommited
const add = (a, b) => a + b;

// Parentheses are optional when only one parameter is present
const getUser = data => data.user;

// However, parentheses are required when no parameters are present
const hello = () => console.log("Hello from AlterClass.io");

Now that we’ve covered the basic syntaxes, let’s get into how arrow functions are used with React. Apart from defining React components as above, arrow functions are also really useful when manipulating arrays, and when working with asynchronous callbacks and Promises.

Indeed, in React we usually have to fetch data from a server and display it to our users. To retrieve this data we often use and chain Promises.

// ES5
fetch(apiURL)
  .then(function(res) {
    return res.json();
  })
  .then(function(data) {
    return data.products;
  })
  .catch(function(error) {
    console.log(error);
  });

Promises chaining is simplified, easier to read, and it is more concise with arrow functions:

// ES6
fetch(apiURL)
  .then(res => res.json())
  .then(data => data.products)
  .catch(error => console.log(error));

Finally, once we have retrieved our data we need to display it. To render a list of data in React, we have to loop inside JSX. This is commonly achieved using the map/reduce/filter array methods.

const products = [
  { _id: 1234, name: "ReactJS Pro Package", price: 199 },
  { _id: 5678, name: "ReactJS Basic Package", price: 99 },
  ...
];
// ES5
function ProductList(props) {
  return (
    <ul>
      {props.products
        .filter(function(product) {
          return product.price <= 99;
        })
        .map(function(product) {
          return <li key={product._id}>{product.name}</li>;
        })}
    </ul>
  );
}

Now, let's see how to achieve the same thing with ES6 arrow functions.

// ES6
const ProductList = props => (
  <ul>
    {props.products
      .filter(product => product.price <= 99)
      .map(product => (
        <li key={product._id}>{product.name}</li>
      ))}
  </ul>
);

Default Parameters

Now that we've seen what are arrow functions, let's talk about default parameters. This ES6+ feature is the ability to initialize functions with default values even if the function call doesn’t include the corresponding parameters.

But first, do you remember how we use to check for undeclared parameters in our functions before ES6? You may have probably seen or used something like this:

// ES5
function getItems(url, offset, limit, orderBy) {
  offset = (typeof offset !== 'undefined') ? offset : 0;
  limit = (typeof limit !== 'undefined') ? limit : 10;
  orderBy = (typeof orderBy !== 'undefined') ? orderBy : 'date';
  ...
}

To prevent our functions from crashing, or to compute invalid/wrong results, we had to write extra code to test each optional parameter and assigned default values. Indeed, this technique was used to avoid undesired effects inside our functions. Without it, any uninitiated parameters would default to a value of undefined.

So, that’s a brief summary of how we handled default parameters prior to ES6. Defining default parameters in ES6 is much easier.

// ES6
function getItems(url, offset = 0, limit = 10, orderBy = 'date') {
  ...
}

// Default parameters are also supported with arrow functions
const getItems = (url, offset = 0, limit = 10, orderBy = 'date') => {
  ...
}

Simple and clean 👌. If offset, limit, and orderBy are passed into the function call, their values will override the ones define as default parameters in the function definition. No extra code needed.

⚠️Be aware that null is considered a valid value. This means that if you pass a null value for one of the arguments, it won't take the default value defined by the function. So make sure to use undefined instead of null when you want the default value to be used.

Now you know how to use default parameters in ES6. What about default parameters and React?

In React, you have the ability to set default values to component props using the defaultProps property. However, this is only available for class components. Actually, the React team is making the defaultProps property on function components deprecated and they will be removed it.

No worries! We can leverage default parameters to set default values to our React function component props. Check out below for an example.

const Button = ({ size = 'md', disabled = false, children }) => (
  <button 
    type="button"
    disabled={disabled}
    className={`btn-${size}`}
  >
    {children}
  </button>
);

Template Literals

Template literals are strings allowing embedded JavaScript expressions. In other words, it is a way to output variables/expressions in a string.

In ES5 we had to break the string by using the + operator to concatenate several values.

// ES5
console.log("Something went wrong: " + error.message);

In ES6, template literals are enclosed by the backtick character instead of double or single quotes. To insert expressions inside those templates, we can use the new syntax ${expression}.

// ES6
console.log(`Something went wrong:  ${error.message}`);
...
console.log(`Hello, ${getUserName()}!`);
...

Template literals are making this kind of substitution more readable. Using them in React will help you set component prop values, or element attribute values, dynamically.

const Button = (props) => (
  <button 
    type="button"
    className={`btn-${props.size}`}
  >
    {props.children}
  </button>
);

Let and Const

In ES5, the only way to declare variables was to use the var keyword. ES6 introduced two new ways to do it with const and let. If you want to learn every detail about those guys, please have a look at this awesome post. Here, I'm just going to list the main differences:

var

  • function scoped
  • hold undefined when accessed a variable before it is declared

let

  • block scoped
  • ReferenceError when accessed a variable before it's declared

const

  • block scoped
  • ReferenceError when accessed a variable before it's declared
  • can't be reassigned
  • should be initialized when declared

Since the introduction of let and const, the rule of thumb is to use them instead of var. You should not use var anymore. Let and const are more specific and give us more predictable variables.

Also, prefer using const over let by default because it cannot be re-assigned or re-declared. Use let when you will need to re-assign the variable.

In a React application, const is used to declare React components as they won't be reassigned. Other than that, variables that should be reassigned are declared with let, and variables that should not be reassigned are declared with const.

const OrderDetails = (props) => {
  const [totalAmount, setTotalAmount] = useState(0.0);
  const { state } = useContext(Context);

  useEffect(() => {
    let total = state.course.price;
    // substract promotional discount
    total -= state.course.price * state.course.discountRate;
    // add taxes
    total += total * state.course.taxPercentage;
    setTotalAmount(total);
  }, 
    [state]
  );

  const handleOnClick = () => { ... };

  return (
    <>
      <span>Total: ${totalAmount}</span>
      <button onClick={handleOnClick}>Pay</button>
    </>
  );
};

Classes

JavaScript classes were introduced with ES6. As stated by the MDN web documentation, classes are "primarily syntactical sugar over JavaScript's existing prototype-based inheritance". Although, there are some properties that are worth knowing as they are not quite the same as class written using regular functions. For that, check this great post.

// ES6 class definition
class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    return `${this.name} says hello!`;
  }
}

// Usage
let user = new User("Greg");
user.greet(); // --> Greg says hello!

An interesting concept related to classes is inheritance. This is not something specific to JavaScript but it is a common concept in object-oriented programming. In short, this is the ability to create a class as a child of another class. The child class will inherit from the properties of its parent (actually this is quite more complex than that depending on the OOP language that you are using).

In ES6, the extends keyword is used to create a class based on another one.

class Employee extends User {
  constructor(name, salary) {
    // call the constructor of the User class
    super(name);
    // add a new property
    this.salary = salary;
  }

  raiseSalary() {
    this.salary += 10000;
    return this.salary;
  }
}

// Usage
let employee = Employee("Greg", 250000);
employee.raiseSalary(); // --> 260000

In React application, you can also use an ES6 class to define a component. To define a React component class, you need to extend the React.Component base class as follow:

class Button extends React.Component {
  render() {
    return <button type="buttom">Click me</button>;
  }
}

By creating components like this, you will have access to a bunch of methods and properties related to React components (state, props, lifecycle methods, ...). Have a look at the React documentation for a detailed API reference of the React.Component class.

promo.png

Destructuring

Destructuring is used very often in React. This is a concept that can be used with objects as well as arrays. Destructuring is an easy way to simplify our JavaScript code because it allows us to pull data out of an object or array in one single line.

Array destructuring is similar to object destructuring except that we pull data out one by one in the order they appear in the array.

Let's jump right into how it is used in a React application.

// grab `useState` with object destructuring
import React, { useState } from 'react';

// grab individual props with object destructuring
const Button = ({ size = 'md', disabled = false }) => { 
  // grab stateful value and update function with array destructing
  const [loading, setLoading] = useState(false);

  return (...);
};

Ternary Operator

The ternary operator is used as a shortcut for the if statement. The syntax of a typical if statement is the following:

if (condition) {
  // value if true
}
else {
  // value if false
}

This is how it looks like using the ternary operator:

condition ? valueIfTrue : valueIfFalse

As you can see this is a much shorter way to define a conditional statement.

If the condition is truthy, the first statement is executed (before the colon :). Otherwise, if the condition is falsy (false, null, NaN, 0, "", or undefined), the second statement is executed (after the colon :).

However, this is not necessarily the cleanest or the more readable way to write conditions. So, be careful when using it as it can become a nightmare to understand, especially if you are chaining multiple conditions as follow.

return condition1 ? value1
         : condition2 ? value2
         : condition3 ? value3
         : value4;

In React, the ternary operator allows us to write more succinct conditional statements in JSX. It is common to use it to decide which component to display or show/hide components based on conditions.

const App = () => {
  const [loading, setLoading] = useState(false);
  const [showPopup, setShowPopup] = useState(false);
  ...

  return (
    <>
      <Navbar />
      {loading ? <Spinner /> : <Body />}
      ...
      {showPopup && <Popup />}
    </>
  );
};

Import / Export Module

Prior to ES6, as they were no native modules support in JavaScript, we used libraries like RequiredJS or CommonJS to import/export modules. You may have probably seen that before, especially if you have already used Node.js.

// ES5 with CommonJS
var express = require('express');
var router = express.Router();

router.get('/', function(req, res) {
  ...
});

module.exports = router;

In ES6, we could natively use the export and import statements to handle modules in our applications.

// auth.js
export const login = (email, password) => { ... };
export const register = (name, email, password) => { ... };

// main.js
import { login, register } from './auth';

This is really useful in React as we are breaking the application UI into a component hierarchy. Components are defined in their own file and required in others such as in the following example:

// Button.js
const Button = ({ size = 'md', disabled = false, children) => (
  <button 
    type="button"
    disabled={disabled}
    className={`btn-${size}`}
  >
    {children}
  </button>
);

export default Button;

// App.js
import Button from './Button';

const App = () => (
  <>
    ...
    <Button size='lg'>Submit</Button>
  </>
);

Async / Await

You might be familiar with the concept of asynchronous programming. In JavaScript, they are quite some ways to work with asynchronous code (callbacks, promises, external libraries such as Q, bluebird, and deferred.js, ...). Here I'm going to talk about async/await only.

Async/await is a special syntax to work with promises in a more comfortable fashion. It is really easy to understand and use.

In case you need to learn about promises, have a look at the MDN doc page.

As you may have noticed, there are two new keywords: async and await.

Let’s start with the async keyword first. Async is used to define an asynchronous function that returns an implicit Promise as its result.

async function myAsyncFunc() {
  return "Hello from AlterClass!";
}

// Usage
myAsyncFunc().then(...);

Note that the syntax and structure of code using async functions look like regular synchronous functions. Simple, right? But wait! There’s another keyword, await.

The keyword await works only inside async function. It makes the program wait until the promise settles and returns its result. Here's an example with a promise that resolves after a few seconds:

async function myAsyncFunc() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("Hello!"), 3000)
  });

  let result = await promise; // wait until the promise resolves

  alert(result); // "Hello!"
}

This is a much more elegant way of getting a promise result than using promise.then(), plus it is easier to read and write.

⚠️Once again, be careful as await can not be used in regular functions. If you do it, you would get a syntax error.

One more thing which is worth mentioning with async/await is how to handle errors. Indeed, if a promise resolves normally, it returns the result. But in case of a rejection, it throws an error. You can either use the promise catch method or try..catch the same way as a regular throw, to handle rejections.

asynFunction().catch(error => console.log(error));

// or

try {
  asynFunction();
} 
catch(error) {
  console.log(error)
}

I have included async/await in this list because, in every front-end project, we are doing a lot of stuff that requires asynchronous code. One common example is when we want to fetch data via API calls.

In React, this is how we could do it using promises + async/await.

const App = () => {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchData() {
      // Check if user is authenticated
      const user = await getUser();
      // Stop loading spinner
      setLoading(false);
    };
    fetchData().catch(alert);
  }, []);

  if (loading) {
    return <Spinner />;
  }

  return <>...</>;
};

Spread Operator / Rest Parameter

The spread operator and the rest parameter are represented by the three dots .... In the case of the spread operator, it expands an iterable into individual elements. For the rest operator, it gathers the rest of the list of arguments into an array.

Let's see some examples to understand how they work and how to use them.

// Rest parameter
function sum(...args) {
  let sum = 0;
  for (let i = 0; i < args.length; i++) {
    sum += args[i];
  }
  return sum;
}

// Spreading elements on function calls
let array = [10, 6, 4];
console.log(Math.max(...array)); // 10

// Copying an array
let items = ['item1', 'item2', 'item3'];
let newArray = [...items];
console.log(newArray); // ['item1', 'item2', 'item3']

// Concatenating arrays
let array1 = ['1', '2', '3'];
let array2 = ['A', 'B', 'C'];
let result = [...array1, ...array2];
console.log(result); // ['1', '2', '3', 'A', 'B', 'C']

// Spread syntax for object literals
var object1 = { _id: 123, name: 'Greg' }
var object2 = { age: 28, country: 'FR'}
const user = { ...object1, ...object2 }
console.log(user); // { "_id": 123, "name": "Greg", "age": 28, "country": "FR" }

The spread operator is highly used in libraries such as Redux to dealing with application state in an immutable fashion. However, this is also commonly used with React to easily pass down all object's data as individual props. This is easier than passing down each prop one by one.

If you have heard about HOC (High-Order Component) before, you know that you need to pass down all the props to the wrapped component. The spread operator is helping with that.

const withStorage = (WrappedComponent) => {
  class WithStorageHOC extends React.Component {
    ...
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
};

Conclusion

In this article, I introduced you to some great ES6+ features to build awesome React applications. Of course, there are many other JavaScript features that you could use, but those 10 are the ones I see and use the most in any React project.

If you liked this post, do not forget to bookmark it and share it with your friends. If you have any questions, feel free to comment below, and follow me for more upcoming posts!

In case you want to learn more about ReactJS and improve your Javascript skills, have a look at my ReactJS online course here. No prior experience in ReactJS needed. You will learn how to build a real-world application step-by-step using modern ReactJS.

promo.png