Spread Operator
What it is
- Operator:
... - Purpose: Expands iterable objects (arrays, strings, etc.) into individual elements.
Common Use
The spread operator is commonly used to append an item to an array in a way that creates a new array and doesn't modify the original one. This pattern is important for maintaining immutability in React state updates.
setTasks([...tasks, task]);
Explanation:
...tasks→ expands all items in the existing array.[...tasks, task]→ creates a new array with all existing items, then addstaskat the end.
Result Example:
tasks = [1, 2, 3];
task = 4;
setTasks([...tasks, task]); // [1, 2, 3, 4]
Why It Matters (React Context)
- Immutable update pattern: avoids mutating the original array.
- Ensures React state updates correctly (React detects new references).
Object Spreading
Spreading works on objects too; it copies key/value pairs from one object into a new one.
const user = { name: "Don", age: 65 };
const copy = { ...user }; // simple copy
Result:
// copy = { name: "Don", age: 65 }
This creates a shallow copy of user. The original object isn’t changed.
You can also modify data in the copy at the same time:
const updated = { ...user, name: "New Name" };
Result:
// updated = { name: "New Name", age: 65 }
Notes
-
Creates a new object; the original remains unchanged.
-
Later keys overwrite earlier ones.
-
Common in React when updating state objects:
setUser({ ...user, loggedIn: true });
Function Arguments
Spread can expand arrays into individual function arguments when a function expects separate parameters.
const numbers = [1, 2, 3];
const max = Math.max(...numbers); // equivalent to Math.max(1, 2, 3)
Why It’s Useful
-
Simplifies calling functions that expect discrete values.
-
Replaces older syntax such as
Math.max.apply(null, numbers)with a cleaner form. -
Useful in wrapper functions and React helpers that forward arguments:
function logAll(...args) { console.log(...args); }
Use in the Wild
Function-argument spreading is moderately common. It appears frequently with built-in utilities like Math.max(...numbers) and in wrapper functions that forward parameters (fn(...args)). You’ll see it often in utility code and React helpers, but less in core application logic.
Destructuring
What it is
Destructuring lets you unpack values from arrays or properties from objects directly into variables.
It’s a concise way to extract data without repeatedly referencing the parent structure.
Object Destructuring
Extracts specific properties from an object.
const user = { name: "Don", age: 65 };
const { name, age } = user;
Result:
// name = "Don"
// age = 65
This creates new variables name and age containing the values from user.
The original object remains unchanged.
Common Uses
-
Pulling data from API responses or config objects.
-
Extracting props in React components:
function UserCard({ name, email }) { return <div>{name} – {email}</div>; }
Array Destructuring
Extracts elements from arrays by position.
const colors = ["red", "green", "blue"];
const [first, second] = colors;
Result:
// first = "red"
// second = "green"
You can skip or collect remaining elements using the rest pattern:
const [first, ...rest] = ["red", "green", "blue"];
// first = "red"
// rest = ["green", "blue"]
Default Values
You can set defaults when the value might be missing.
const settings = { theme: "dark" };
const { theme = "light", layout = "grid" } = settings;
// theme = "dark"
// layout = "grid"
Variable Swapping (Array Trick)
Destructuring can also swap values without a temp variable.
let a = 1, b = 2;
[a, b] = [b, a];
// a = 2, b = 1
Why It Matters
- Reduces boilerplate for accessing properties or array positions.
- Keeps React code (hooks, props) compact and readable.
- Plays well with the spread operator — spread expands, destructuring extracts.
Use in the Wild
Destructuring is very common in modern JavaScript and React. It’s elegant once you recognize the pattern, but can confuse readers unfamiliar with ES6 syntax. In React, you’ll see it constantly in function parameters, hook calls, and state handling.
Memory Management
How it works
- JavaScript uses automatic garbage collection.
- When no references remain to an object (like the old
tasksarray), its memory is reclaimed.
Example
setTasks([...tasks, task]);creates a new array.- The variable
tasksnow points to the new array. - The old array becomes unreferenced and eligible for garbage collection.
Key Notes
- Garbage collection timing is managed by the JS engine (non-deterministic).
- Usually happens in the background.
- Important in large apps for performance and leak prevention.
JavaScript & TypeScript Promises
What They Are
A Promise represents the eventual result of an asynchronous operation.
You can reframe it as:
A Promise guarantees a callback after the work completes with either a success or a failure result.
This replaces the uncertainty of callbacks with a consistent pattern you can chain and handle predictably.
States
| State | Meaning |
|---|---|
| Pending | Waiting for completion |
| Fulfilled | Completed successfully |
| Rejected | Failed with an error |
Before vs With Promises
// Callback style (no guarantees)
doSomething(function(result) { ... })
// Promise style (guaranteed callback)
doSomething()
.then(result => { ... })
.catch(error => { ... })
Creating a Promise
const myPromise = new Promise((resolve, reject) => {
// async work
if (ok) resolve(result)
else reject(error)
})
Consuming a Promise
fetchData()
.then(data => console.log(data)) // success
.catch(err => console.error(err)) // failure
.finally(() => console.log('done')) // always
Chaining
Each .then() returns a new Promise:
fetchUser()
.then(u => fetchOrders(u.id))
.then(o => process(o))
.catch(e => handleError(e))
Async / Await (Modern Syntax)
async function getData() {
try {
const res = await fetch(url)
return await res.json()
} catch (e) {
console.error(e)
}
}
Same behavior, cleaner syntax.
Common Methods
| Method | Purpose |
|---|---|
.then() |
Handle success |
.catch() |
Handle failure |
.finally() |
Always run |
Promise.all() |
Wait for all to finish |
Promise.any() |
First success |
Promise.allSettled() |
Wait for all outcomes |
Promise.resolve(x) |
Create resolved promise |
Promise.reject(e) |
Create rejected promise |
TypeScript Tip
const getUser = (): Promise<User> =>
new Promise((resolve, reject) => { ... })
Async functions automatically return Promise<T>.
Key Points
- Promises guarantee a callback result — success or failure.
- They run after current code finishes.
- Once fulfilled or rejected, the state doesn’t change.
- Always handle errors with
.catch()ortry/catch.