1. Variables: var, let, and const
Variables allow us to store data.
JavaScript provides three ways to declare variables:
varletconst
var
var is the older way of declaring variables.
var framework = "React";
console.log(framework);
// React
However, var has some important problems.
For example, it is function-scoped rather than block-scoped.
if (true) {
var framework = "React";
}
console.log(framework);
// React
The variable escapes the if block.
This behavior can create unexpected bugs.
let
let allows you to declare variables that can be reassigned.
let age = 25;
age = 26;
console.log(age);
// 26
Unlike var, let is block-scoped.
if (true) {
let framework = "React";
}
console.log(framework);
// ReferenceError
const
const declares a variable that cannot be reassigned.
const framework = "React";
framework = "Vue";
// TypeError
However, const does not make objects immutable.
const user = {
name: "Alice",
};
user.name = "Bob";
console.log(user.name);
// Bob
The object can be modified because the reference remains the same.
But you cannot reassign the variable:
const user = {
name: "Alice",
};
user = { name: "Bob" };
// TypeError
Which one should you use?
In modern JavaScript:
- Use
constby default. - Use
letwhen reassignment is necessary. - Avoid
varin modern code.
You'll see const everywhere in React.
2. Data Types
JavaScript has several primitive data types.
const name = "Alice"; // String
const age = 25; // Number
const isDeveloper = true; // Boolean
const score = null; // Null
let result; // Undefined
const id = Symbol("id"); // Symbol
const bigNumber = 123n; // BigInt
JavaScript also has objects.
const user = {
name: "Alice",
age: 25,
};
Arrays are also objects in JavaScript.
const numbers = [1, 2, 3];
console.log(typeof numbers);
// "object"
Understanding undefined vs. null
undefined usually means a value hasn't been assigned.
let username;
console.log(username);
// undefined
null represents an intentionally empty value.
const selectedUser = null;
One unusual JavaScript behavior:
console.log(typeof null);
// "object"
This is a historical JavaScript quirk.
3. Operators and Comparisons
JavaScript supports arithmetic operators:
const a = 10;
const b = 3;
console.log(a + b); // 13
console.log(a - b); // 7
console.log(a * b); // 30
console.log(a / b); // 3.333...
console.log(a % b); // 1
Equality operators
Always understand the difference between == and ===.
console.log(5 == "5");
// true
console.log(5 === "5");
// false
== performs type coercion.
=== compares values without performing that coercion.
Prefer === and !== in modern JavaScript.
Logical operators
const isLoggedIn = true;
const isAdmin = false;
console.log(isLoggedIn && isAdmin);
// false
console.log(isLoggedIn || isAdmin);
// true
console.log(!isLoggedIn);
// false
These operators are particularly useful in React.
4. Truthy and Falsy Values
JavaScript doesn't always require a Boolean value in conditions.
Some values behave like true, while others behave like false.
Falsy values
The main falsy values are:
false
0
-0
0n
""
null
undefined
NaN
Everything else is generally truthy, including empty arrays and empty objects.
console.log(Boolean([]));
// true
console.log(Boolean({}));
// true
Example
const username = "";
if (username) {
console.log("Username exists");
} else {
console.log("Username is empty");
}
This concept becomes extremely important when using conditional rendering in React.
5. Scope: Where Variables Exist
Scope determines where a variable can be accessed.
JavaScript has several important types of scope.
Global scope
const framework = "React";
function showFramework() {
console.log(framework);
}
showFramework();
// React
The function can access the global variable.
Function scope
function example() {
const message = "Hello";
console.log(message);
}
example();
// Hello
console.log(message);
// ReferenceError
The variable exists only inside the function.
Block scope
if (true) {
const message = "Hello";
let framework = "React";
}
console.log(message);
// ReferenceError
Variables declared with let and const are block-scoped.
6. Hoisting
Hoisting describes JavaScript's behavior of processing declarations before executing code.
It does not mean JavaScript physically moves your code.
var hoisting
console.log(framework);
var framework = "React";
// undefined
The declaration is available, but the assignment hasn't happened yet.
Conceptually, this behaves similarly to:
var framework;
console.log(framework);
framework = "React";
let and const
console.log(framework);
let framework = "React";
// ReferenceError
Variables declared with let and const are also processed before execution, but they cannot be accessed before their declaration is initialized.
This period is called the Temporal Dead Zone (TDZ).
Function declarations
Function declarations can be called before their definition.
sayHello();
function sayHello() {
console.log("Hello!");
}
This works because function declarations are hoisted with their function definition.
7. Functions
Functions are reusable blocks of code.
Function declaration
function add(a, b) {
return a + b;
}
console.log(add(5, 3));
// 8
Function expression
const add = function (a, b) {
return a + b;
};
console.log(add(5, 3));
// 8
Arrow functions
Arrow functions are widely used in modern JavaScript and React.
const add = (a, b) => {
return a + b;
};
For a single expression, you can simplify the syntax:
const add = (a, b) => a + b;
Single parameter
const square = number => number * number;
console.log(square(4));
// 16
Returning an object
Remember to wrap the object in parentheses:
const createUser = (name) => ({
name: name,
active: true,
});
console.log(createUser("Alice"));
Arrow functions and this
Arrow functions do not create their own this.
They inherit this from the surrounding lexical scope.
This is especially important when working with callbacks and React event handlers.
8. Callback Functions
A callback is a function passed into another function.
function greet(name, callback) {
console.log(`Hello, ${name}`);
callback();
}
function sayGoodbye() {
console.log("Goodbye!");
}
greet("Alice", sayGoodbye);
Output:
Hello, Alice
Goodbye!
Callbacks are everywhere in JavaScript.
For example:
const numbers = [1, 2, 3];
numbers.map((number) => number * 2);
The arrow function is a callback passed to map().
React uses callbacks for event handlers:
<button onClick={() => console.log("Clicked!")}>
Click me
</button>
9. Closures
A closure happens when a function remembers variables from its surrounding scope, even after that outer function has finished executing.
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter());
// 1
console.log(counter());
// 2
console.log(counter());
// 3
Why does this work?
The returned function still has access to the count variable from createCounter().
This is a closure.
Closures are fundamental to understanding many JavaScript patterns, including how React hooks preserve state between renders.
10. Arrays
Arrays store multiple values in a single variable.
const frameworks = ["React", "Vue", "Angular"];
console.log(frameworks[0]);
// React
Important array methods
map()
Transforms every element.
const numbers = [1, 2, 3];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);
// [2, 4, 6]
filter()
Returns elements that satisfy a condition.
const numbers = [1, 2, 3, 4];
const evenNumbers = numbers.filter((number) => number % 2 === 0);
console.log(evenNumbers);
// [2, 4]
find()
Returns the first matching element.
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
const user = users.find((user) => user.id === 2);
console.log(user);
// { id: 2, name: "Bob" }
reduce()
Accumulates values.
const numbers = [1, 2, 3, 4];
const total = numbers.reduce((sum, number) => sum + number, 0);
console.log(total);
// 10
some() and every()
const numbers = [2, 4, 6];
console.log(numbers.some((number) => number > 5));
// true
console.log(numbers.every((number) => number % 2 === 0));
// true
These methods are used constantly when working with API data and rendering lists in React.
11. Objects
Objects store data using key-value pairs.
const user = {
name: "Alice",
age: 25,
role: "Frontend Developer",
};
Access properties
console.log(user.name);
// Alice
console.log(user["age"]);
// 25
Add properties
user.country = "Germany";
Update properties
user.age = 26;
Delete properties
delete user.role;
Objects are essential for understanding React props, state, and API responses.
12. Destructuring
Destructuring allows you to extract values from arrays and objects.
Object destructuring
const user = {
name: "Alice",
age: 25,
role: "Developer",
};
const { name, age } = user;
console.log(name);
// Alice
console.log(age);
// 25
Rename properties
const { name: username } = user;
console.log(username);
// Alice
Default values
const { country = "Unknown" } = user;
console.log(country);
// Unknown
Array destructuring
const colors = ["red", "green", "blue"];
const [first, second] = colors;
console.log(first);
// red
console.log(second);
// green
Destructuring in React
You'll frequently see this:
function UserProfile({ name, age }) {
return (
<div>
<h2>{name}</h2>
<p>{age}</p>
</div>
);
}
This is object destructuring applied to function parameters.
13. Spread Operator (...)
The spread operator expands values from arrays or objects.
Copy an array
const numbers = [1, 2, 3];
const copiedNumbers = [...numbers];
console.log(copiedNumbers);
// [1, 2, 3]
Combine arrays
const first = [1, 2];
const second = [3, 4];
const combined = [...first, ...second];
console.log(combined);
// [1, 2, 3, 4]
Copy and update an object
const user = {
name: "Alice",
age: 25,
};
const updatedUser = {
...user,
age: 26,
};
console.log(updatedUser);
// { name: "Alice", age: 26 }
Why is this important in React?
React state should generally be updated immutably.
Instead of modifying an existing object:
user.age = 26;
You create a new object:
const updatedUser = {
...user,
age: 26,
};
This pattern is fundamental to React state updates.
14. Rest Parameters
The rest operator collects multiple arguments into an array.
function sum(...numbers) {
return numbers.reduce((total, number) => total + number, 0);
}
console.log(sum(1, 2, 3, 4));
// 10
The function can accept any number of arguments.
Rest in destructuring
const user = {
name: "Alice",
age: 25,
country: "Germany",
};
const { name, ...otherDetails } = user;
console.log(name);
// Alice
console.log(otherDetails);
// { age: 25, country: "Germany" }
Spread vs. Rest
They use the same syntax but serve different purposes.
- Spread expands values.
- Rest collects values.
15. Template Literals
Template literals allow you to create strings using backticks.
const name = "Alice";
const age = 25;
const message = `My name is ${name} and I'm ${age} years old.`;
console.log(message);
Output:
My name is Alice and I'm 25 years old.
You can also write multiline strings:
const message = `
Hello!
Welcome to JavaScript.
`;
Template literals are useful when displaying dynamic content.
16. Optional Chaining (?.)
Optional chaining allows you to safely access properties that may not exist.
Imagine receiving this data:
const user = {
name: "Alice",
address: {
city: "Berlin",
},
};
Without optional chaining:
console.log(user.address.city);
But what if address doesn't exist?
const user = {
name: "Alice",
};
console.log(user.address.city);
// TypeError
Using optional chaining:
console.log(user.address?.city);
// undefined
Multiple levels
const user = {};
console.log(user?.address?.city);
// undefined
Optional function calls
user.onClick?.();
This calls onClick only if it exists and is callable.
Optional chaining is particularly useful when working with API data.
17. Nullish Coalescing (??)
The nullish coalescing operator provides a default value when the left side is null or undefined.
const username = null;
const displayName = username ?? "Guest";
console.log(displayName);
// Guest
Difference between || and ??
const count = 0;
console.log(count || 10);
// 10
console.log(count ?? 10);
// 0
Why?
|| uses the fallback for any falsy value.
?? uses the fallback only for null or undefined.
This distinction matters when zero or an empty string is a valid value.
18. The Ternary Operator
The ternary operator is a concise way to write conditional expressions.
const age = 20;
const message = age >= 18 ? "Adult" : "Minor";
console.log(message);
// Adult
The syntax:
condition ? valueIfTrue : valueIfFalse;
React example
function LoginStatus({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? "Welcome back!" : "Please log in."}
</div>
);
}
This is one of the most common conditional rendering patterns in React.
19. Short-Circuit Evaluation
JavaScript logical operators can return values, not just Booleans.
Using &&
const isLoggedIn = true;
isLoggedIn && console.log("Welcome!");
The second expression executes only when the first expression is truthy.
React example
function Notifications({ count }) {
return (
<div>
{count > 0 && <p>You have {count} notifications.</p>}
</div>
);
}
Important React warning
Be careful when using numbers with &&.
function Notifications({ count }) {
return (
<div>
{count && <p>You have notifications.</p>}
</div>
);
}
If count is 0, React may render the number 0.
A safer version:
{count > 0 && <p>You have notifications.</p>}
20. Modules: import and export
JavaScript modules allow you to split code into multiple files.
Export a function
// utils.js
export function add(a, b) {
return a + b;
}
Import the function
// main.js
import { add } from "./utils.js";
console.log(add(2, 3));
// 5
Default exports
// User.js
export default function User() {
return "Alice";
}
Import it:
import User from "./User.js";
Named vs. default exports
Named export:
export const name = "Alice";
Import:
import { name } from "./user.js";
Default export:
export default name;
Import:
import username from "./user.js";
Modules are fundamental to React applications because components are usually organized into separate files.
21. DOM Manipulation
The DOM (Document Object Model) represents the structure of an HTML document.
JavaScript can interact with the DOM.
Select an element
const heading = document.querySelector("h1");
Change text
heading.textContent = "Hello JavaScript!";
Add an event listener
const button = document.querySelector("button");
button.addEventListener("click", () => {
console.log("Button clicked!");
});
Example
<button id="counter">Click me</button>
<script>
const button = document.querySelector("#counter");
button.addEventListener("click", () => {
button.textContent = "Clicked!";
});
</script>
What changes when you learn React?
In traditional JavaScript, you often manipulate the DOM directly.
In React, you usually describe what the UI should look like based on data and state.
Instead of manually changing elements, React manages UI updates for you.
This is one of the biggest conceptual differences between vanilla JavaScript and React.
22. Events
Events allow JavaScript to respond to user interactions.
Examples include:
clickinputsubmitchangekeydown
Vanilla JavaScript
const button = document.querySelector("button");
button.addEventListener("click", function () {
console.log("Clicked!");
});
React event handling
function App() {
function handleClick() {
console.log("Clicked!");
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
Notice the difference:
- Vanilla JavaScript uses
addEventListener. - React uses event props such as
onClick.
Also, React expects a function reference:
<button onClick={handleClick}>
Click me
</button>
Not an immediate function call:
<button onClick={handleClick()}>
Click me
</button>
The second version executes the function during rendering.
23. Promises
JavaScript often performs asynchronous operations.
Examples include:
- Fetching API data.
- Reading files.
- Waiting for a timer.
- Sending network requests.
A Promise represents the eventual completion or failure of an asynchronous operation.
A Promise can be:
- Pending
- Fulfilled
- Rejected
Example
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Operation successful!");
} else {
reject("Something went wrong!");
}
});
Using .then()
promise
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
Promises are fundamental to working with APIs in React.
24. async and await
async and await make asynchronous JavaScript easier to read.
Using .then()
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((users) => {
console.log(users);
})
.catch((error) => {
console.error(error);
});
Using async and await
async function getUsers() {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
const users = await response.json();
console.log(users);
} catch (error) {
console.error(error);
}
}
getUsers();
Important details
-
asyncmakes a function return a Promise. -
awaitpauses that async function until the Promise settles. -
try...catchhelps handle errors.
await doesn't block the entire JavaScript application.
Example
async function getUser() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/1"
);
const user = await response.json();
return user;
}
You'll use these concepts frequently when fetching data in React.
25. Fetch API
The Fetch API allows JavaScript to make HTTP requests.
GET request
async function getUsers() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
if (!response.ok) {
throw new Error("Failed to fetch users");
}
const users = await response.json();
return users;
}
POST request
async function createUser() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Alice",
email: "alice@example.com",
}),
}
);
const user = await response.json();
return user;
}
Important
fetch() does not automatically reject the Promise for HTTP errors such as 404 or 500.
You should check:
response.ok
This is important when handling API errors.
26. Error Handling
JavaScript uses try...catch to handle errors.
try {
const result = riskyOperation();
console.log(result);
} catch (error) {
console.error("Something went wrong:", error);
}
Example with asynchronous code
async function getData() {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error("Request failed");
}
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
Error handling is important when working with API requests and asynchronous operations.
27. Immutability
Immutability means creating new values rather than modifying existing ones.
JavaScript does not automatically make objects immutable.
Mutable example
const user = {
name: "Alice",
age: 25,
};
user.age = 26;
The original object has been modified.
Immutable update
const user = {
name: "Alice",
age: 25,
};
const updatedUser = {
...user,
age: 26,
};
Now you have a new object.
Why does React care?
React state updates should generally replace objects or arrays with new references rather than mutate existing state.
For example:
const numbers = [1, 2, 3];
const updatedNumbers = [...numbers, 4];
This creates a new array.
Understanding immutability will make React state much easier to understand.
28. Shallow Copy vs. Deep Copy
The spread operator creates a shallow copy.
const user = {
name: "Alice",
address: {
city: "Berlin",
},
};
const copiedUser = { ...user };
The top-level object is copied, but the nested object is still shared.
copiedUser.address.city = "Paris";
console.log(user.address.city);
// Paris
This happens because both objects reference the same nested object.
Using structuredClone()
For supported data types, you can create a deep copy:
const copiedUser = structuredClone(user);
Now nested objects are copied too.
Note that structuredClone() has limitations and does not clone every possible JavaScript value, such as functions.
29. this Keyword
The this keyword refers to a context determined by how a function is called.
Object method
const user = {
name: "Alice",
greet() {
console.log(`Hello, ${this.name}`);
},
};
user.greet();
// Hello, Alice
Here, this refers to the object before the dot.
Important
Arrow functions do not have their own this.
const user = {
name: "Alice",
greet: () => {
console.log(this.name);
},
};
user.greet();
This does not behave like the previous example.
For object methods that need the object's this, use a regular method or function.
In React function components, you usually don't need to manually manage this, unlike older class-based React components.
30. Classes and Constructors
JavaScript supports class syntax.
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, ${this.name}`;
}
}
const user = new User("Alice", 25);
console.log(user.greet());
// Hello, Alice
Classes are useful to understand because JavaScript is a multi-paradigm language.
However, modern React primarily uses function components rather than class components.
You don't need to master classes before learning modern React.
31. Set and Map
JavaScript also provides built-in data structures.
Set
A Set stores unique values.
const numbers = new Set([1, 2, 2, 3, 3, 4]);
console.log(numbers);
// Set(4) { 1, 2, 3, 4 }
Convert it into an array:
const uniqueNumbers = [...numbers];
console.log(uniqueNumbers);
// [1, 2, 3, 4]
Map
A Map stores key-value pairs.
const userRoles = new Map();
userRoles.set("Alice", "Admin");
userRoles.set("Bob", "User");
console.log(userRoles.get("Alice"));
// Admin
Don't confuse:
-
Array.prototype.map()— transforms array elements. -
Map— a separate JavaScript data structure.
32. Type Coercion
JavaScript sometimes converts values automatically.
This is called type coercion.
console.log("5" + 2);
// "52"
The number becomes a string.
But:
console.log("5" - 2);
// 3
Here, JavaScript converts the string to a number.
Explicit conversion
const number = Number("42");
console.log(number);
// 42
Other useful conversions:
String(123);
Number("123");
Boolean(1);
Understanding type coercion helps you avoid unexpected bugs.
33. JavaScript Execution and the Event Loop
JavaScript commonly runs code on a single main thread.
But it can handle asynchronous operations using browser or runtime APIs and the event loop.
Consider:
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
console.log("End");
Output:
Start
End
Timeout
Even though the timer is set to 0 milliseconds, its callback doesn't execute immediately.
The callback runs later when the event loop allows it.
Promises and microtasks
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Output:
Start
End
Promise
Timeout
Promise callbacks run as microtasks, which are processed before timer callbacks such as setTimeout().
You don't need to master every detail of the event loop before React, but understanding asynchronous execution is extremely helpful.
JavaScript Concepts You Should Master Before React
Here's a practical checklist.
Essential
- [ ]
let,const, and variable declarations - [ ] Primitive data types
- [ ] Objects and arrays
- [ ] Functions and arrow functions
- [ ] Scope and closures
- [ ] Destructuring
- [ ] Spread and rest operators
- [ ] Template literals
- [ ] Array methods:
map,filter,find,reduce - [ ] Conditional statements
- [ ] Ternary operator
- [ ] Logical operators
- [ ] Modules:
importandexport - [ ] Promises
- [ ]
asyncandawait - [ ] Fetch API
- [ ] Error handling
- [ ] Immutability
Helpful
- [ ] Hoisting and Temporal Dead Zone
- [ ] Optional chaining
- [ ] Nullish coalescing
- [ ] DOM manipulation
- [ ] Event handling
- [ ]
thiskeyword - [ ] Classes
- [ ] Set and Map
- [ ] Event loop
Practice Projects Before React
Before starting React, try building these small projects using vanilla JavaScript.
1. Todo App
Practice:
- Arrays
- Objects
map()filter()- DOM manipulation
- Events
2. Weather App
Practice:
- Fetch API
- Promises
- Async/Await
- Error handling
- API data
3. Shopping Cart
Practice:
- Objects
- Arrays
reduce()- Spread operator
- Immutability
4. User Search
Practice:
filter()includes()- Input events
- Array manipulation
5. Counter
Practice:
- Variables
- Functions
- Events
- DOM updates
These projects will help you understand how JavaScript works before React introduces its own abstractions.
Final Thoughts
Learning React without understanding JavaScript is like trying to build a house without understanding the materials you're using.
You don't need to know every advanced JavaScript concept before starting React.
But you should feel comfortable with:
- Functions
- Objects
- Arrays
- Destructuring
- Spread syntax
- Array methods
- Closures
- Promises
- Async/Await
- Modules
- Immutability
Once these concepts become familiar, React will feel much more natural.
Don't memorize JavaScript. Learn how to think in JavaScript.
React will come next.
Happy coding! 🚀
Top comments (0)