Telerik blogs

This post covers concepts every JavaScript developer should be comfortable with before learning React. Although there are still many concepts you should be familiar with to be a better React developer, the ones mentioned here are almost always what you’ll run into when you write React.

The React documentation defines React as a JavaScript library for building user interfaces. I’ve heard many people say they “learned React” before learning JavaScript, and honestly, that feels like throwing things at the wall and seeing what sticks.

To avoid or better understand bugs without asking questions like, “why is React reacting this way,” it’s better to start with understanding the fundamental principles or concepts of JavaScript that React was built on. This way, if—or better still, when—you encounter bugs, you’ll know if the issue is with React or JavaScript. Let’s get started!

Logical Operators

There are three logical operators in JavaScript. The logical AND operator (&&), the logical OR operator (||), and the logical NOT operator (!).

You can use these operators to compare variables, and they return the boolean values true or false, allowing you to make decisions or perform actions based on the result of the comparison. They can also be used with values of any data type.

  1. Logical OR ( || ): This operator returns the first truthy value from a list of variables, expressions or operands starting from left to right. It converts the operands into boolean values. If the result is true, it stops and returns the value of the operand; else, it returns the last operand if all operands result in false.
true || false; // true
false || false; // false
false || true; // true
false || true || false; // true
"" || "Ifeoma"; // 'Ifeoma'
undefined || null || 0; // 0
  1. Logical AND ( && ): Evaluates from left to right. If it encounters a falsy value, it stops and returns the value; else, it returns the last operand if all operands have been evaluated.
false && true; // false
1 && 0 && 1; // 0
"" && null; // ''
undefined && true; // undefined
  1. Logical NOT ( ! ): It is a unary operator (accepts only one operand) popularly known as the “bang” operator. First, it converts its operand to a boolean and returns the inverse value.
!0; // returns true. 0 is falsy, and the inverse is truthy
!"Ifeoma"; // returns false. 'Ifeoma' is truthy, and the inverse is falsy
!!null; // returns false. null is falsy. The first bang operator returns true, and the second bang operator returns false
!undefined; // returns true. Since undefined is falsy, the inverse is truthy

Let’s take an example that shows how we would typically use the logical operators in a vanilla JavaScript application.

const username = "user";
const password = 1234;

if (username == "admin" && password == "pass") {
  console.log("Hello Admin");
}
// nothing is printed to the console because the `username` is not equal to `admin`; hence the ‘&&’ operator evaluates to false, and the code block associated with it is not executed.

const username = "user";
const password = "1234";

if (username == "user" || password == 5678) {
  console.log("Hello Admin");
}
// 'Hello User' hets printed in the console because one condition ( username = ‘user’) passed. Hence the logical ‘||’ evaluates to true.

There are many instances in React where you may use the logical operators, ranging from conditional rendering of components to setting default values for variables, etc. You are likely to come across snippets like this:

import { useState } from "react";
import { RegularUser, SpecialUser, Login } from "./components/utils";

const Example = () => {
  const [loggedIn, setLoggedIn] = useState(true);
  const [subscribed, setSubscribed] = useState(false);

  return (
    <div>
      {loggedIn && subscribed && <SpecialUser />} // <SpecialUser /> is not
      rendered because one of the two conditions ( subscribed ) did not pass
      {loggedIn || (subscribed && <RegularUser />)} // <RegularUser /> is rendered
      because one of the two conditions (loggedIn) passed
      {!loggedIn && !subscribed && <Login />} // The <Login /> component would
      only be rendered if the user is not logged in and has not subscribed.
    </div>
  );
};

Nullish Coalescing Operator

ES2020 introduced the nullish coalescing operator, which is a logical operator represented by two question marks (??). It accepts two operands and evaluates the operand on the right side of the expression only if the operand of the left side of the expression is null or undefined.

    let nullishValue = null
    let emptyVal = ''
    let num = 43
    console.log(nullishValue ?? emptyVal) // logs '' because the left hand operand is null

    console.log(emptyVal ?? num) // logs '' because the left hand operand is not null or undefined

    // This operator can also be used to specify a default value for a variable
    const logAgeToConsole = (age) => {
      age = age ?? 25
      console.log(age)
    }

    logAgeToConsole(21) // logs 21 to the console.
    logAgeToConsole() // logs 25 to the console as a default value

Let’s look at how we can use the nullish coalescing operator in React.

    export function AwesomeComponent({ numOfChildren }){
      let chilrenCount = numOfChildren ?? 'Not Specified'
      return (
           <p> { childrenCount } </p>
      )
    }

    // Let's look at different scenarios and their corresponding output.
    <AwesomeComponent numOfChildren = '' /> // childrenCount is set to ''
    <AwesomeComponent numOfChildren = 5 /> // childrenCount would be 5 
    <AwesomeComponent /> // childrenCount would be set to 'Not Specified'
    <AwesomeComponent numOfChildren = 0 /> //childrenCount would be 0
    <AwesomeComponent numOfChildren = {false} /> // chilrenCount would be false
    <AwesomeComponent numOfChildren = null /> // ChildrenCount would be set to 'Not Specified'

From the scenarios above, Not Specified is only assigned to the variable childrenCount when the values passed to it are null, undefined or not specified. The Nullish coalescing operator does not consider other falsy values.

Rest and Spread Operators

Although the two operators are somewhat different, JavaScript uses the three dots (…) to represent both the rest and spread operators. The rest operator bundles the user-supplied arguments or parameters into a single array. On the other hand, the spread operator is used to expand a list of iterables (objects, arrays, etc. ) into its items.

The Rest Operator

In JavaScript, built-in methods like the max and min methods of the Math object and user-defined functions can accept any number of arguments to perform their respective operations. To pass an array of values, you could take advantage of the rest operator like the one below:

const numbers = [3, 2, 1, -2, 4, 9];
console.log(Math.max(...numbers)); // logs 9 to the console

const myFunction = (one, two, ...rest) => {
  console.log(rest);
};
myFunction("one", "two", "three", "four", "five"); // logs [‘three’, ‘four’, ‘five’] to the console

The Spread Operator

The spread operator works in a way that allows you to expand an iterable such as an array or string in places where zero or more arguments are allowed. The code snippet below shows how you would typically use the spread operator to copy items from a source to a destination.

const details = ["my", "name", "is"];
const message = [...details, "Ifeoma Imoh"];
console.log(message); // logs [ 'my', 'name', 'is', 'Ifeoma Imoh' ]

const oldObj = { firstName: "Ifeoma", lastName: "Imoh" };
const newObj = { ...oldObj, middleName: "Sylvia" };
console.log(newObj); // prints { firstName: 'Ifeoma', lastName: 'Imoh', middleName: 'Sylvia'}

The spread operator is convenient when you want to copy one object into another without mutating the original one. The concept of mutable and immutable values is discussed in a later section.

const initialState = { counter: 0 };
const myReducer = (state = initialState, action) => {
  switch (action.type) {
    case "increment":
      return { ...state, counter: state.counter + 1 };
    case "decrement":
      return { ...state, counter: state.counter - 1 };
    default:
      return state;
  }
};

As seen above, the spread operator copies all the properties from the state object into a new object, and only the counter property of the new object is changed or updated.

The following code snippet shows how to use the rest operator in React with props passed to a component.

const ChildComponent = ({ name, ...props }) => {
  return (
    <div>
      <p> Welcome, {name} </p>
      <p>{sex}</p>
      <p> {height} </p>
    </div>
  );
};

const ParentComponent = () => {
  return <ChildComponent name="Ifeoma" sex="F" height="173cm" />;
};

In the snippet above, only the name property is destructured; the other properties are collected as one object.

Destructuring

Destructuring is a JavaScript expression that allows us to unpack or extract data from arrays, objects, maps or sets into new variables without changing or mutating the original element. Arrays and objects are the two most commonly used data structures in JavaScript, so we will focus on them in this section.

Object Destructuring

Object destructuring follows a specific pattern. We have an existing object that we want to split (destructure) on the right. On the left is an object-like pattern corresponding to the properties you want to extract.

let user = {
  name: "Ifeoma Imoh",
  age: 100,
  height: 173,
};

// destructuring each property from the main object
let { name, age, height } = user;
console.log(name); // logs Ifeoma Imoh to the console.
console.log(age); // logs 100 to the console.
console.log(height); // logs 173 to the console.

In the code above, properties user.name, user.age and user.married are assigned to name, age and married, respectively. It is worth noting that the order in which you destructure the properties does not matter. The code below works exactly like the one above.

let { height, name, age } = user;

The benefit of object destructuring is that it allows you to reassign properties destructured from an object to another variable name. It comes in handy when renaming a lengthy property name.

let user = {
  name: "Ifeoma Imoh",
  age: 100,
  height: 173,
};

let { name: n, age: a, height: h } = user;
console.log(n); // logs Ifeoma Imoh to the console..
console.log(a); // logs 100 to the console.
console.log(h); // logs 173 to the console.

Array Destructuring

Like object destructuring, array destructuring allows us to reduce the items in an array into individual elements that can be accessed by their variable name.

let myArray = [1, 2];
let [one, two] = myArray;
console.log(one); // prints 1 to the console.
console.log(two); // prints 2 to the console.

We can also destructure a nested array in the same fashion. It would look something like this:

let array = ["welcome", "to", "the", ["class", "office", "market"]];
const [, , , location] = array; // to get the fourth item in the array
let [first, , third] = location; // destructuring the first and third item
console.log(first); // logs ‘class’ to the console
console.log(third); // logs ‘market’ to the console

When writing React as a beginner, your first encounter with destructuring might be the props object passed to a component. Typically, the code looks like this:

const ChildComponent = (props) => {
  // props received from the parent  component
  const { name, age, level, rating } = props; // unpacking the individual items from the props object
  return (
    <div></div>
    // some JSX
  );
};
const ParentComponent = () => {
  return <ChildComponent name="John Doe" age="34" level="14" rating="3" />;
};

The common useState pattern is also array destructuring:

const [state, setState] = React.useState();

Arrow Functions

The 2015 edition of the ECMAScript spec, popularly known as ES6, added some new features to the JavaScript language. Arrow functions are one of them.

Arrow functions differ from the regular function declaration in several ways, including how their syntax is expressed and how their scopes are determined. They come in handy when you need to pass a function, usually an anonymous function, as an argument to another function (as in the case of higher-order functions). The syntactic abbreviation of arrow functions can improve the readability of your code.

// How functions are declared before es6 arrow functions
function sum(a, b) {
  return a + b;
}
// Arrow function Syntax is:
const functionName = (para1, para2) => expression;
// e.g
let sum = (a, b) => {
  return a + b;
};
// Or
let sum = (a, b) => a + b;

When using arrow functions, if the function accepts only one argument, you can omit the parentheses around the arguments to shorten the code and make it more precise.

//regular function
function logger(msg) {
  console.log(msg);
}
// arrow function
const logger = (msg) => console.log(msg);

In React, arrow functions can be used to create components, and they can be used as callback functions, set event listeners, etc.

The code below shows how to replace a component created with the function declaration syntax with an arrow function.

    // Regular function
    function RegularFunction() {
      return (
        // some jsx here...
      );
    }
    // Using Arrow function
    const ArrowFunction = () => (
      // more cleaner and you can omit the return keyword
      // some jsx here...
    );

Another common use of arrow functions is when setting an event listener:

    // function declaration more verbose
    <Button onClick = { function(){console.log('clicked')} } />
    // arrow function less verbose
    <Button onClick = { () => console.log('clicked') }  />

Conditional Ternary Operator

The JavaScript ternary operator is the only operator in JavaScript that takes three arguments. It is a shorter alternative to the standard if-else statement.

The syntax is as follows:

condition_to_evaluate
  ? expression_if_condition_is_true
  : expression_if_condition_is_false;

The first expression is the condition to evaluate, which should return either true or false. The second expression is the code to execute if the predefined condition evaluates to true. Finally, the expression on the right of the colon represents the code to run if the condition evaluates to false.

Let’s write an example using if-else and convert it to use the ternary operator.

let age = 35;

if (age <= 18) {
  console.log("you are not eligible to vote");
} else {
  console.log("you are eligble to vote");
}

// Using ternary operator
let age = 35;
age <= 18
  ? console.log("you are not eligible to vote")
  : console.log("you are eligble to vote");

As seen above, the ternary operator is cleaner and shorter in syntax compared to the regular if-else expression. But what if you have more conditions to evaluate? Take, for instance, the code below:

let score = 85;
let grade;

if (score >= 80) {
  grade = "A";
} else if (score > 70) {
  grade = "B";
} else if (score > 60) {
  grade = "C";
} else {
  grade = "D";
}
console.log("Your grade is " + grade);

// Using ternary operator. The code looks like this:
let score = 85;
let grade;

score >= 80
  ? (grade = "A")
  : score >= 70
  ? (grade = "B")
  : score >= 60
  ? (grade = "C")
  : (grade = "D");

console.log("Your grade is " + grade);

It is not recommended to replace a nested if-else with the ternary operator because it could get messier or unreadable. For such cases, it is best to use an if-else or a switch statement.

In React, the ternary operator can be used to conditionally render components depending on whether or not a condition is met.

userLoggedIn ? <UserDashboard /> : <Login />;

Assuming the UserDashboard and Login are both components in our React app, we use the ternary operator to render the UserDashboard component if the user is logged in; else, we render the Login component.

Callback Functions

In JavaScript, functions are first-class objects. In other words, they can be treated the same way as variables. A callback function, in its most basic form, is a function that is passed as an argument to another function. This allows a function to call another function, usually after performing asynchronous tasks like retrieving data from a remote endpoint, handling events, etc. The parent function or the function that invokes the callback is referred to as a higher-order function (HOF).

Many of JavaScript’s built-in functions accept a callback as an argument—e.g., setTimout, setInterval, addEventListener—and array methods like find, filter, some, map, forEach, etc.

Let’s look at the setTimeout function, which takes a callback function and a timer (usually called the delay in milliseconds) as an argument. It executes the callback once the delay elapses.

setTimeout(() => {
  console.log("Called after 1 min");
}, 1000);

The code above logs “Called after 1 min” to the console after 1 minute (1000 ms).

Let’s also look at how we can use callback functions with event listeners. Event listeners are used to register callback functions that will be executed when the event being listened for occurs on a target element. Its first argument is usually the name of the event, and its second argument is the callback function to run when the event (such as “click”) happens.

const btn = document.querySelector(".addTodo");
const myFunction = () => console.log("I was clicked");
btn.addEventListener("click", myFunction);

The concept of callback, as discussed above for vanilla JavaScript, also holds for React. A good number of hooks introduced in React 16 accept callback functions.

The most common one among them is the useEffect hook. It takes a callback function as its first argument and an array of dependencies as the second.

export default function Home() {
  const [cldImages, setCldImages] = useState([]);

  useEffect(() => {
    getAllImages();
  }, []);

  async function getAllImages() {
    try {
      const images = await axios.get("/api/getImages");
      setCldImages(images.data);
    } catch (error) {
      console.log(error);
    }
  }
}

Promises

Promises are the basis of handling asynchronous operations/tasks in JavaScript. The syntax is cleaner and easier to work with when compared to callbacks.

Promises are special objects in JavaScript that allow you to associate callback functions to the eventual successful or unsuccessful completion of an asynchronous operation. Promises can be created by creating a new instance of the Promise object and passing a callback function to the constructor like this:

const myPromise = new Promise(callback);
function callback(resolve, reject) {
  if (1 + 1 === 2) {
    resolve();
  } else {
    reject();
  }
}

The callback function passed to the Promise constructor receives two callback functions: resolve and reject. The resolve is called if everything goes fine; otherwise, the reject function is called.

Promises can be in one of four states.

  1. resolve – When the code associated with the promise completes successfully. And the resolve callback is called.
  2. reject – When the code associated with the promise fails, the reject callback function is called.
  3. pending – When the promise has neither been resolved nor rejected.
  4. fulfilled – When the promise has either been resolved or rejected.

Your promise may also return information (values) when it resolves or rejects by taking the return values as arguments.

const myPromise = new Promise(callback);
function callback(resolve, reject) {
  // perform some asynchronous operations
  let sum = 1 + 1; // let assume this an asynchronous operation
  if (sum === 2) {
    resolve("1 + 1 is actually equal to 2");
  } else {
    reject("ooops! An error occured");
  }
}

Now you may be wondering how you’d know if a promise succeeds or fails or how to access the information or values returned by the resolve or reject callback functions. It turns out that JavaScript also attaches three method handlers to the Promise object. These are then, catch and finally method handlers, which we can use to do just that.

To find out if the promise succeeded or failed and what information (value) was returned as a result of the promise we created above, run the code below:

myPromise
  .then((info) => {
    console.log(info); // logs ‘1 + 1 is actually equal to 2’ to the console
  })
  .catch((info) => {
    console.log(info); // prints ‘ooops! Something crazy.  1 + 1 is no more equal to 2’ to the console
  })
  .finally(() => {
    console.log(
      "finally is sure to always execute because it executes if the promise succeeds or rejects
    );
  });

Promises have so many use cases in React. For instance, you are likely to come across a code snippet like the one below when fetching data from a remote endpoint:

import React, { useState, useEffect } from "react";

function MyComponent() {
  const [names, setNames] = useState([]);

  useEffect(() => {
    fetch("http://some-remote-endpoints")
      .then((response) => respone.json())
      .then((data) => setNames(data))
      .catch((error) => console.log(error));
  }, []);

  return (
    <div>
      {names.map((name, index) => (
        <li key={index}> {name} </li>
      ))}
    </div>
  );
}

In the snippet above, we make an API request to the remote endpoint. If the operation succeeds (resolves), the then function on line 8 is executed, and the response is converted to a JSON object. This conversion of the response to a JSON object happens to be another async operation. If this conversion process succeeds (resolves), the then function on line 9 executes, and the names state is set to the result of the operation. If any of the two async operations fail (rejects), the catch function on line 10 is executed, and the error is printed to the console.

Promises are great for handling asynchronous operations and avoiding callback hell. Going further, ES2017 introduced another cool feature called async/await, which is a more elegant way to handle asynchronous operations than promises.

Async/Await

Async/Await was introduced to make working with promises easier and cleaner. The syntax is simple and straightforward.

    async function myFunction (){
      await //some asynchronous operations goes here.
    }
    //using an arrow function
    const myFunction =  async () => {
      await //some asynchronous operation goes here.
    }

There is one rule: The await keyword must be used in a function, and that function must be marked as async with the async keyword.

Let’s convert the example in the Promises section above to use async/await:

import React, { useState, useEffect } from "react";

function MyComponent({ continent }) {
  const [names, setNames] = useState([]);

  useEffect(() => {
    async function myFunction() {
      try {
        const res = await fetch("https://some-remote-endpoints");
        const data = await res.json();
        setNames(data);
      } catch (e) {
        console.log(e);
      }
    }
    myFunction();
  }, []);

  return (
    <div>
      {names.map((name, index) => (
        <li key={index}> {name} </li>
      ))}
    </div>
  );
}

You may have noticed how we use the await keyword to precede the lines that involve an asynchronous operation instead of using .then as in promises. The await keyword tells JavaScript to pause the execution of the async function until the asynchronous code is fulfilled (either resolved or rejected). If the asynchronous code rejects, the code in the catch block is executed.

Array Methods

Arrays are used to store a list or collection of values. Each value in an array is called an element and is specified by an index. Arrays are one of JavaScript’s most important and frequently used data structures. In this section, we’ll go over the basic yet important array methods JavaScript developers should be familiar with.

Map

This is an array method that allows you to iterate over each element of an array while performing some manipulation on them. The map function returns a new array and does not change the original array.

let myArray = [1, 2, 3, 4, 5];
let newArray = myArray.map((item, index) => {
  return (item = item * 2);
});
console.log(newArray); // logs [2,4,6,8, 10] to the console

Filter

The filter method works exactly like the map but only returns the items or elements of an array that meets a specific condition.

let myArray = [1, 2, 3, 4, 5];
const newArray = myArray.filter((item, index) => {
  return index > 2;
});
console.log(newArray); // logs [4,5] to the console. As they have an index of 3 and 4, respectively

Find

The find method returns the first item of the list that satisfies the given condition and immediately terminates or returns undefined if none of the items satisfies the condition(s) specified.

let myArray = [1, 2, 3, 4, 5];
const result = myArray.find((item, index) => {
  return index > 2;
});
console.log(result); // logs 4 to the console. It is the first item in the list with an index greater than 2.

See here for other commonly used array methods.

Below is an example showing how to use some of these array methods in React.

import React, { useState } from "react";

const People = () => {
  const [people, setPeople] = useState([
    { id: 1, name: "John", sex: "Not specified" },
    { id: 2, name: "Mary", sex: "F" },
    { id: 3, name: "Esther", sex: "F" },
    { id: 4, name: "Doe", sex: "M" },
    { id: 5, name: "Alex", sex: "M" },
  ]);

  return (
    <div>
      <p>All</p>
      {people.map((person, index) => {
        return <li key={index}> {person.name} </li>;
      })}
      <p>Female</p>
      {people.filter((person, index) => {
        person.sex == "F" && <li> {person.name} </li>;
      })}
      <p>Alex</p>
      {people.find(
        (person) => person.name == "Alex" && <li> {person.name} </li>
      )}
    </div>
  );
};

Shorthand Names

Another cool feature introduced in ES6 is shorthand property and method names.

Shorthand Property Name

The shorthand property name is used when a variable name is the same as the value of a property of an object. You can omit the property name like the one below:

// without shorthand property name
const myFunction = ({ name, age, status }) => {
  return {
    name: name,
    age: age,
    status: status,
  };
};

// using shorthand property name
const myFunction = ({ name, age, status }) => {
  return {
    name,
    age,
    status,
  };
};

Shorthand Method Name

A function that is a property on an object is called a method. Traditionally to declare a method inside an object, we write this this:

const myFunction = ({ name, age, status }) => {
  return {
    name: name,
    age: age,
    status: status,
    logger: function () {
      // the function keyword is explicitly stated
      console.log("Tradional way of writing methods");
    },
  };
};
// logger method is declared in line 6 without the shorthand syntax

With the introduction of the shorthand method name in ES6, we can now omit the function keyword like this:

const myFunction = ({ name, age, status }) => {
  return {
    name: name,
    age: age,
    status: status,
    logger() {
      // function keyword omitted
      console.log("shorthand method name without function keyword");
    },
  };
};
// logger method in line 6 using shorthand method name

Optional Chaining

The optional chaining operator allows you to safely access deeply nested properties of an object without having to explicitly check if each reference in the chain is valid. It works exactly like the . operator, except if a reference is undefined or null, it returns undefined instead of throwing an error.

const myObj = {
  name: { firstName: "Ifeoma", lastName: "Imoh" },
};

console.log(myObj.location.street); // throws an error -- cannot read property of undefined because location is not defined in myObj

console.log(myObj.location?.street); // safely prints undefined but doesn’t throw any error.

In the example above, JavaScript implicitly checks to ensure that location exists before accessing the street property.

Let’s see an example in React.

    import { useLocation } from "react-router-dom";
    import React, { useState } from "react";

    function ReceivingComponent() {
      const [name, setName] = useState("");
      const location = useLocation();
      const firstName = location.state.data?.firstName;
      useEffect(() => {
        setName(firstName);
      }, [firstName]);
      return (
        //some jsx here...
      );
    }

In the code above, we check if the data property is present in the state object before accessing its firstName property. Even when the data property is not defined in the state object, the line of code will simply return undefined, but it won’t throw an error.

Switch Statement

The switch statement is a clean and readable alternative to having multiple if/else statements in your program. The switch statement evaluates an expression, matches or compares the outcome with case values, and executes the code block associated with the matching case value.

switch (expression) {
  case a:
    // code to execute if the expression evaluates to a
    break;
  case b:
    // code to execute if the expression evaluates to b
    break;
  case c:
    // code to execute if the expression evaluates to c
    break;
  default:
  // code to execute if the expression does not match the defined cases.
}

How it works:

  • JavaScript first evaluates the expression inside of parentheses after the switch keyword.
  • Secondly, it compares the result of the expression with the case values in a top to bottom approach using strict comparison ( === ).
  • JavaScript then executes the statement in the case branch whose value matches the result of the switch expression.
  • JavaScript stops comparing the switch expression against the case values once it finds a match. If no matching case is found, it executes the code in the default block.
  • The break statement sufficiently denotes the end of each case block. If omitted, JavaScript continues executing the statement in each of the following case blocks even after finding a match.

Classes

Classes in JavaScript are a blueprint for creating objects. They were introduced in the ECMAScript 2015 (ES6). They are described as syntactic sugar over the prototype-based inheritance paradigm used to implement OOP concepts.

A class declaration starts with the class keyword, followed by your class name. Then your properties and methods declaration goes inside curly braces. Like this:

class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  greetUser() {
    alert("Good day" + this.name);
  }
}

const user2 = new User("John Doe", 30);
user2.greetUser(); // alerts Good day John Doe on the screen.

There are two types of React components: class and functional components. The following is an example of a React class component:

class MyReactComponent extends React.Component {
  constructor() {
    // some logic goes here...
  }
  render() {
    // some jsx goes here...
  }
}

Mutable vs. Immutable Values

In this section, we will go over the concepts of mutability and immutability in JavaScript and how to leverage immutability in JavaScript to make your code less prone to errors. It’s very easy to alter the value of your variables accidentally. Hence the need to use immutable data.

To properly understand mutable and immutable values, you first need to understand that JavaScript has two kinds of data types. The primitive types (string, integers, booleans) are passed by value and immutable, while reference types (e.g., objects and arrays) are passed by reference and are mutable.

Let’s take some examples:

// Example 1 with primitive type
let a = "John";
let b = a; // assigning a to b, so b is 'John' too
b = "Doe"; // changing b from 'John' to 'Doe'
console.log(a); // logs John to the console
console.log(b); // logs Doe to the console

On line 3, the variable a was assigned to variable b. We know that the value a holds type string which is a primitive data type, and primitives are passed by value. That means only the value a holds—John was passed to variable b.

In line 3, we changed the value b holds from John to Doe. Then in lines 4 and 5, we logged a and b to the console, and as expected, we got John and Doe, respectively. Changing the value that b holds doesn’t change the value that a holds. That’s immutability with primitive types.

Let’s take a look at another example using a reference type:

// example 2 with reference type
let a = { name: "John" };
let b = a; // assigning a to b, so b = { name:'John'} as well.
b.name = "Doe"; // changing the value of b from { name:'John' } to { name:'Doe' }
b.age = 24; // adding an age property to b
console.log(a); // logs { name: 'Doe', age: 24 } to the console
console.log(b); // logs { name: 'Doe', age: 24 }} to the console

In the example above, we assigned the value of a to b. Since a is of type object and objects are passed by reference, updating bb.name = 'Doe' also automatically updates a.

We also added an age property to b, which also updated a as we can see from what we logged to the console. This is because on line 3 when we assigned a to b, the memory reference of a was passed to b, not just the value, which means that both variables now refer to the same address in memory and any changes to one will automatically reflect in the other.

Conclusion

In this article, we covered concepts that every JavaScript developer should be comfortable with before learning React. Although there are still many concepts, you should be familiar with to be a better React developer, the ones mentioned above are almost always what you’ll run into when you write React.


Ifeoma-Imoh
About the Author

Ifeoma Imoh

Ifeoma Imoh is a software developer and technical writer who is in love with all things JavaScript. Find her on Twitter or YouTube.

Related Posts

Comments

Comments are disabled in preview mode.