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!
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.
true || false; // true
false || false; // false
false || true; // true
false || true || false; // true
"" || "Ifeoma"; // 'Ifeoma'
undefined || null || 0; // 0
false && true; // false
1 && 0 && 1; // 0
"" && null; // ''
undefined && true; // undefined
!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>
);
};
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.
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.
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 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 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 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.
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();
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') } />
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.
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 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.
resolve
– When the code associated with the promise completes successfully. And the resolve callback is called.reject
– When the code associated with the promise fails, the reject callback function is called.pending
– When the promise has neither been resolved nor rejected.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 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.
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.
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
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
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>
);
};
Another cool feature introduced in ES6 is shorthand property and method names.
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,
};
};
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
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.
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:
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...
}
}
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 b
—b.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.
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.