Mastering modern ECMAScript (ES6+) foundations essential for professional React development: scoping with let and const, immutable array operations (map, filter, reduce), object destructuring, spread and rest patterns, arrow functions, ES Modules, and asynchronous workflows with Promises, async/await, and Fetch.
In modern React engineering, legacy var is strictly obsolete. React applications rely exclusively on block-scoped const and let to ensure deterministic scoping, eliminate accidental variable re-declarations, and enforce state immutability conventions.
| Keyword | Scope | Re-assignable? | Re-declarable? | Hoisting Behavior | Usage in Modern React |
|---|---|---|---|---|---|
const |
Block { ... } |
No (Constant Binding) | No | Hoisted to Temporal Dead Zone (TDZ) | Default Choice: Components, hooks, props, handlers, imports |
let |
Block { ... } |
Yes | No | Hoisted to Temporal Dead Zone (TDZ) | Used strictly when variable values change (e.g., loop counters, accumulators) |
var |
Function / Global | Yes | Yes (Error-Prone) | Hoisted and initialized to undefined |
Banned: Causes scope leaks and unpredictable re-render bugs |
In JavaScript, const creates an immutable variable identifier binding, not an immutable value! If you declare const user = { name: "Alice" }, you cannot re-assign user = ..., but you can mutate user.name = "Bob".
In React, directly mutating object properties bypasses React's shallow comparison engine, preventing components from re-rendering! You must always treat const objects as immutable by creating new copies using the spread operator.
JavaScript classifies data into seven Primitive Types (String, Number, BigInt, Boolean, Undefined, Null, Symbol) passed by value, and Reference Types (Objects, Arrays, Functions) passed by memory address.
Always use === (strict equality) and !==. Strict equality compares both value and data type without performing dangerous implicit type coercion:
0 == false; // true (Loose: implicit type coercion!)
0 === false; // false (Strict: Number !== Boolean)
null === undefined; // false
Standard if/else statements cannot be written inside JSX curly braces. The ternary operator is the primary tool for inline conditional UI rendering:
// JSX Conditional Rendering
<div>
{isLoggedIn ? <Dashboard /> : <LoginForm />}
</div>
Used to render elements only when a boolean condition is met. If the left operand is true, the right operand is rendered:
// Renders badge only when hasUnread is true
<div>
{hasUnread && <NotificationBadge />}
</div>
Gotcha: If the left value is the number 0, JavaScript short-circuits and prints the literal number 0 on screen! Always use boolean coercions: unreadCount > 0 && ....
Prevents catastrophic "Cannot read properties of undefined" runtime crashes when accessing asynchronous API responses:
// Safe deep object access
const city = user?.address?.city;
// Nullish coalescing: fallback only if null or undefined
const displayName = user?.nickname ?? "Anonymous Guest";
In modern React, every component is a JavaScript function, and event handlers are passed as function callbacks.
| Feature | Function Declaration | ES6 Arrow Function |
|---|---|---|
| Syntax | function greet(name) { return "Hi " + name; } |
const greet = (name) => "Hi " + name; |
| Implicit Return | Not supported (must use explicit return keyword) |
Supported for single-line expressions without curly braces |
this Binding |
Dynamic: defined by how the function is called at runtime | Lexical: inherits this from surrounding parent scope |
| Standard React Role | Often used for top-level component definitions | Dominant for inline event callbacks, custom hooks, and array transforms |
// 1. Implicit return of JSX element
const UserCard = ({ name }) => <div className="card">{name}</div>;
// 2. Passing arguments in event handlers via inline arrow function
function ItemList({ items, onDelete }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
{/* We wrap in an arrow function so it executes ONLY upon click */}
<button onClick={() => onDelete(item.id)}>
Delete
</button>
</li>
))}
</ul>
);
}
In React, state must remain immutable. Mutating methods like array.push(), array.pop(), and array.splice() modify arrays in-place and are strictly avoided in React state updates. Instead, React developers rely on non-mutating methods that return brand-new array instances:
Transforms every element in an array into a new corresponding item. In React, map() is used in 99% of components to transform arrays of data objects into lists of JSX elements.
const fruits = ["Apple", "Banana", "Cherry"];
const listItems = fruits.map((fruit, idx) => (
<li key={idx}>{fruit}</li>
));
Returns a new array containing only elements that satisfy a truth condition. The universal pattern for deleting an item from React state by ID.
// Deleting user with id 2 immutably
const users = [{id:1}, {id:2}, {id:3}];
const updated = users.filter(u => u.id !== 2);
// Result: [{id:1}, {id:3}]
Reduces an array down to a single accumulated value (such as calculating the total price in a shopping cart component).
const cart = [
{ price: 20 },
{ price: 35 },
{ price: 15 }
];
const total = cart.reduce((acc, item) => acc + item.price, 0);
// Result: 70
find(): Returns the first element matching a predicate (e.g., finding the active user object by ID).some(): Returns a boolean indicating whether at least one element satisfies a condition (e.g., checking if any form field is invalid).every(): Returns true only if all elements pass a test (e.g., verifying all required terms-of-service checkboxes are checked).Destructuring is an ES6 syntax that unpacks values from arrays or properties from objects into distinct variables in a single, clean expression.
Unpacks named properties from objects. Used in virtually every React component signature to read props directly:
// Without destructuring
function UserProfile(props) {
return <h1>{props.name} ({props.role})</h1>;
}
// With Destructuring & Default Values
function UserProfile({ name, role = "Viewer", age }) {
return <h1>{name} ({role})</h1>;
}
// Renaming during destructuring
const response = { status_code: 200, payload: "Success" };
const { status_code: status, payload: data } = response;
Unpacks elements based on their positional index. This is the exact mechanism powering React Hooks such as useState:
// How useState works under the hood
function useState(initialValue) {
let val = initialValue;
function setVal(newVal) { val = newVal; }
return [val, setVal]; // Returns a 2-element array
}
// Array destructuring lets YOU choose variable names!
const [count, setCount] = useState(0);
const [theme, setTheme] = useState("dark");
Although both use three consecutive dots (...), their purpose is determined by context:
Expands the elements of an existing array or properties of an object into a new container. Essential for maintaining React state immutability:
// 1. Immutably copying & adding to an array
const originalList = [1, 2, 3];
const newList = [...originalList, 4]; // [1, 2, 3, 4]
// 2. Immutably updating nested state in React
const [user, setUser] = useState({ name: "Alice", role: "Dev", score: 85 });
// Updating score WITHOUT mutating previous properties:
setUser(prev => ({
...prev,
score: 95
}));
Condenses multiple remaining elements or properties into a single array or object. Heavily used in higher-order wrapper components:
// Forwarding unknown props to an underlying HTML button
function CustomButton({ label, variant = "primary", ...restProps }) {
return (
<button className={`btn-${variant}`} {...restProps}>
{label}
</button>
);
}
// Usage accepts any valid HTML attribute (id, disabled, onClick)
<CustomButton label="Submit" disabled={true} onClick={handleClick} />
Delimited by backticks (`...`) instead of standard quotes, Template Literals enable multi-line strings and embedded JavaScript expressions via the ${expression} syntax:
// Composing dynamic CSS classes based on component state
function AlertBadge({ type, isDismissible, children }) {
// Evaluates variables and ternary expressions inside the string
const cssClasses = `alert-box alert-${type} ${isDismissible ? "has-close-btn" : "static-box"}`;
return <div className={cssClasses}>{children}</div>;
}
React applications are organized into modular files. JavaScript supports two primary module export paradigms:
| Feature | Default Export | Named Export |
|---|---|---|
| Declaration Syntax | export default function App() { ... } |
export const Button = () => { ... } |
| Import Syntax | import App from "./App.jsx"; (No curly braces) |
import { Button } from "./Button.jsx"; (Must use curly braces) |
| Limit per File | Maximum one default export per file | Unlimited named exports per file |
| Renaming on Import | Can be imported under any name: import MyApp from ... |
Requires explicit alias: import { Button as MyButton } from ... |
| Convention in React | Used for primary page or component files (e.g., App.jsx) |
Used for utility libraries, icon collections, and helper functions |
Browsers are single-threaded. Fetching user data from an API or querying a database takes time. Promises represent the eventual completion (or failure) of an asynchronous operation.
Initial status; the background operation has begun (e.g., HTTP packet traveling across the internet) but has not yet resolved or failed.
The operation completed successfully. The promise returns a resolved value accessed via .then() or await.
The operation failed (e.g., 404 Not Found, network timeout). The promise returns an error handled via .catch() or try/catch.
Introduced in ES2017, async/await is syntactic sugar built on top of native Promises, eliminating nested .then() callback chains:
// Async function returns a Promise automatically
async function fetchUserProfile(userId) {
try {
// Execution pauses here until the network request resolves
const response = await fetch(`https://api.example.com/users/${userId}`);
// Check HTTP status code (200-299)
if (!response.ok) {
throw new Error(`HTTP network error: status ${response.status}`);
}
// Await JSON deserialization
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to load user profile:", error.message);
throw error; // Re-throw to be caught by UI state handler
} finally {
console.log("Network consultation finished.");
}
}
The native browser Fetch API provides a promise-based interface for making HTTP requests (GET, POST, PUT, DELETE). Data exchanged across web APIs is encoded in JSON (JavaScript Object Notation).
JSON is a plain string format. JavaScript provides two global utility methods:
JSON.stringify(object): Converts an in-memory JavaScript object into a JSON string (used when sending data in POST request bodies or saving to localStorage).JSON.parse(string): Deserializes a raw JSON string back into a living JavaScript object.Sending new data to a backend server requires configuring headers and serializing payloads:
const createPost = async (newPost) => {
const res = await fetch("https://api.example.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer token123"
},
body: JSON.stringify(newPost)
});
return await res.json();
};
Below is a complete, runnable JavaScript ES6+ module demonstrating all key concepts—destructuring, spread immutability, arrow functions, async/await, and array operations—working in harmony:
// ==============================================================================
// DevAscent Academy - Unit 2: JavaScript Essentials for React
// Master Blueprint of Modern ES6+ Features in Action
// ==============================================================================
// 1. Initial State Data (Array of Objects)
const initialInventory = [
{ id: 101, name: "Mechanical Keyboard", price: 120, category: "Hardware", inStock: true },
{ id: 102, name: "Wireless Mouse", price: 60, category: "Hardware", inStock: false },
{ id: 103, name: "Ultra-Wide Monitor", price: 450, category: "Hardware", inStock: true },
{ id: 104, name: "Desk Mat", price: 25, category: "Accessories", inStock: true }
];
// 2. Destructuring & Arrow Functions
const formatProduct = ({ name, price, inStock }) => {
const availability = inStock ? "Available" : "Out of Stock";
return `${name} - ${price} USD (${availability})`;
};
// 3. Non-Mutating Array Operations
// Filter: Get only available items
const availableItems = initialInventory.filter(item => item.inStock);
// Map: Transform items into formatted strings
const productListStrings = availableItems.map(formatProduct);
// Reduce: Calculate total inventory value of in-stock items
const totalInventoryValue = availableItems.reduce((acc, item) => acc + item.price, 0);
// 4. Spread Operator for Immutable Updates
const newProduct = { id: 105, name: "USB-C Hub", price: 45, category: "Hardware", inStock: true };
const updatedInventory = [...initialInventory, newProduct];
// 5. Asynchronous Data Fetching Simulation
const simulateFetchUserData = async (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, username: "dev_guru", role: "Frontend Lead" });
} else {
reject(new Error("Invalid User ID supplied"));
}
}, 100);
});
};
// 6. Async Execution
async function runDemo() {
console.log("--- Available Products ---");
productListStrings.forEach(str => console.log("•", str));
console.log(`\nTotal In-Stock Valuation: ${totalInventoryValue} USD`);
try {
const user = await simulateFetchUserData(42);
// Object destructuring with fallback
const { username, role, department = "Engineering" } = user;
console.log(`\nActive User: ${username} | Role: ${role} | Dept: ${department}`);
} catch (err) {
console.error("Execution error:", err.message);
}
}
runDemo();
--- Available Products ---
• Mechanical Keyboard - 120 USD (Available)
• Ultra-Wide Monitor - 450 USD (Available)
• Desk Mat - 25 USD (Available)
Total In-Stock Valuation: 595 USD
Active User: dev_guru | Role: Frontend Lead | Dept: Engineering
Answer: In React, state is compared using shallow equality (reference comparison by memory pointer). When you call array.push(newItem), you mutate the existing array in-place, but its reference memory address remains identical. When React compares oldState === newState, it sees the exact same pointer address and assumes nothing has changed, completely skipping the component re-render. To trigger a re-render, you must create a brand-new array reference using the spread operator ([...prevArray, newItem]) or non-mutating methods like map and filter.
Answer: Both let and const are block-scoped variables introduced in ES6. let allows re-assignment, while const creates an immutable binding that cannot be re-assigned.
The Temporal Dead Zone (TDZ) is the temporal window between entering a block scope and the actual line of code where the variable is declared. Accessing a let or const variable while inside its TDZ throws a ReferenceError, unlike legacy var which hoisted and initialized variables to undefined.
Answer: Standard function declarations have dynamic this binding: their this value is determined dynamically by how and where the function was called at runtime.
Arrow functions do not have their own this context; they bind this lexically, inheriting the this value from their enclosing lexical scope at the time they are defined. This makes arrow functions ideal for event listeners, component callbacks, and array transforms where you need to preserve outer scope context.
Answer:
A file can have at most one default export (export default MyComponent;), and the consumer can import it without curly braces under any arbitrary identifier name (import App from "./MyComponent";).
A file can have multiple named exports (export const Button = ...; export const Card = ...;), and the consumer must import them inside curly braces using the exact matching exported names (import { Button, Card } from "./Components";) unless explicitly renamed using the as alias keyword.
Answer:
Optional Chaining (?.) safely short-circuits property access: if an intermediate object property is null or undefined, it immediately returns undefined instead of throwing a fatal runtime TypeError.
Nullish Coalescing (??) provides a fallback default value strictly when the left-hand expression evaluates to null or undefined, safely preserving falsy but valid values like 0, false, or "" (which the older logical OR || operator would mistakenly override).
Answer: A Promise is an object representing the eventual resolution or failure of an asynchronous operation (possessing Pending, Fulfilled, or Rejected states).
async/await is preferred because it allows asynchronous, non-blocking code to be written and read like clean synchronous sequential statements. It completely eliminates deeply nested .then() callback chains ("Promise Hell") and allows unified error handling using standard try/catch/finally blocks.
const for 90% of bindings; use let only when re-assignment is mandatory; never use legacy var.[...array, newItem], { ...object, key: value }, map(), and filter() to generate new reference pointers.map() renders JSX list hierarchies; filter() eliminates items immutably by ID.props cleanly in component parameters and unpack [state, setState] tuples from React hooks.?.) and nullish coalescing (??) to handle pending API data gracefully without crashes.async/await enclosed in robust try/catch blocks for clean error management.