React Engineer Learning Roadmap
const, let, var
🧠 Detailed Explanation
In JavaScript, you use const, let, and var to create variables — a container to store values.
- const: Use this when you never want the variable to change. It’s constant.
- let: Use this when the value can change later, like a counter or input.
- var: This is the old way. It’s still valid but can create bugs because of how it works with scope.
In short:
- ✅ Use
const
when you don’t want the value to change. - ✅ Use
let
when the value needs to change. - ❌ Avoid
var
— it’s confusing in modern code.
For example:
// const: fixed value
const name = "Ali";
// let: can change
let count = 1;
count = 2;
// var: works like let, but has older rules
var city = "Lahore";
Think of const
as a locked box 📦 — you can look inside, but you can’t swap what’s in it.
let
is like a backpack 🎒 — you can add and remove things freely.
💡 Examples
Example 1: Using const
for constant values
const pi = 3.14159;
console.log(pi); // 3.14159
// ❌ Reassignment not allowed:
pi = 3.14; // Error: Assignment to constant variable
Why: Use const
when you don’t want the value to change. It makes your code more secure.
Example 2: Using let
for values that change
let score = 0;
score += 10;
console.log(score); // 10
Why: Use let
when the value needs to update — like in counters, input fields, or loops.
Example 3: Using var
(not recommended)
var name = "Ali";
var name = "Aisha"; // No error — but not good!
console.log(name); // "Aisha"
Why: var
lets you redeclare variables in the same scope. This can cause unexpected bugs, so modern code avoids using it.
Example 4: Block Scope with let
and const
if (true) {
let local = "inside";
const fixed = 42;
}
console.log(local); // ❌ Error: local is not defined
console.log(fixed); // ❌ Error: fixed is not defined
Why: let
and const
stay only inside the block they’re written in (like inside if
, for
, etc.).
Example 5: const
with arrays and objects
const numbers = [1, 2, 3];
numbers.push(4); // ✅ Allowed
console.log(numbers); // [1, 2, 3, 4]
// ❌ Reassigning the array itself is not allowed:
numbers = [5, 6]; // Error
Why: You can change the content inside a const
object or array, but you can’t reassign the whole thing.
🔁 Alternatives
Always prefer const
by default. Use let
only when reassignment is needed. Avoid var
unless required for legacy code.
❓ General Questions & Answers
Q1: What’s the difference between const
, let
, and var
?
A:
– const
is used when the value should never be reassigned.
– let
is used when the value can change.
– var
is outdated and has strange behaviors with scope.
Always try to use const
or let
. Avoid var
unless you’re maintaining legacy code.
Q2: Can I use const
with objects and arrays?
A: Yes! But remember, const
means you can’t reassign the variable itself — but you can still change what’s inside the object or array.
const user = { name: "Ali" };
user.name = "Aisha"; // ✅ Allowed
user = { name: "New" }; // ❌ Error
Q3: Why is var
considered bad practice?
A: Because var
is function-scoped, not block-scoped. That means it ignores block boundaries like if
, for
, or while
, which can cause weird bugs.
if (true) {
var x = 10;
}
console.log(x); // 10 😬 (Shouldn't be visible outside)
Q4: Should I always use const
?
A: Yes — use const
by default. Then switch to let
if you realize you need to reassign it. This keeps your code safer and cleaner.
Q5: What happens if I declare the same variable twice?
A:
– With var
: You can declare it again — which is risky.
– With let
and const
: You’ll get an error, which helps catch bugs early.
var name = "Ali";
var name = "Aisha"; // ✅ Works, but dangerous
let age = 20;
let age = 25; // ❌ Error
🛠️ Deep Dive Technical Q&A
Q1: Why does var
leak outside of blocks?
A: Because var
is function-scoped, not block-scoped. So it “ignores” curly braces from if
, for
, etc.
if (true) {
var message = "I leak!";
}
console.log(message); // ✅ "I leak!"
Solution: Use let
or const
to safely limit scope:
if (true) {
let safe = "Hidden inside";
}
console.log(safe); // ❌ ReferenceError
Q2: Can I update values declared with const
if it’s an object?
A: Yes. const
prevents reassignment of the variable itself, but you can still modify object or array contents.
const user = { name: "Ali" };
user.name = "Aisha"; // ✅ Allowed
user = {}; // ❌ Error: assignment to constant variable
Q3: How do let
and var
behave inside loops?
A: var
creates one shared variable, while let
creates a new one on each loop iteration.
// Using var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 1000);
}
// Logs: var: 3, var: 3, var: 3
// Using let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log("let:", i), 1000);
}
// Logs: let: 0, let: 1, let: 2
Solution: Use let
to preserve values inside loops.
Q4: What happens if I use a variable before declaring it?
A: With var
, it’s undefined due to hoisting. With let
/const
, you’ll get an error (temporal dead zone).
console.log(a); // undefined
var a = 5;
console.log(b); // ❌ ReferenceError
let b = 10;
console.log(c); // ❌ ReferenceError
const c = 15;
Q5: Can I declare variables with the same name in the same scope?
A: var
allows redeclaration, let
and const
do not.
var x = 1;
var x = 2; // ✅ Allowed
let y = 1;
let y = 2; // ❌ SyntaxError
const z = 1;
const z = 2; // ❌ SyntaxError
✅ Best Practices with Examples
1. Use const
by default
Declare everything with const
unless you specifically plan to reassign it.
// ✅ Good
const name = "Ali";
// ❌ Unnecessary use of let
let name = "Ali";
2. Use let
only when reassignment is needed
This includes counters, form values, toggles, etc.
let count = 0;
count++; // ✅ allowed and expected
3. Avoid var
completely in modern JavaScript
It leads to confusing behavior with scope and hoisting. Use let
or const
instead.
// ❌ Bad
var title = "Old JS";
// ✅ Modern
const title = "Modern JS";
4. Use block-scoped variables for safer code
let
and const
are block-scoped. This avoids variable leaks.
if (true) {
let message = "Only inside this block";
}
console.log(message); // ❌ Error: not defined
5. Don’t redeclare variables in the same scope
This is allowed with var
, but avoided with let
and const
, which is good for catching bugs.
// ❌ Dangerous
var status = "pending";
var status = "approved";
// ✅ Safe
const status = "approved"; // Declare once
6. Use const
with arrays and objects
Even if the content will change, you want to prevent reassigning the reference.
const users = [];
users.push("Ali"); // ✅ works
users = []; // ❌ Error
7. Declare variables as close as possible to where they’re used
This improves readability and prevents bugs in long functions.
// ✅ Good
function greet() {
const name = "Ali";
console.log(name);
}
// ❌ Bad
const name = "Ali";
function greet() {
console.log(name);
}
🌍 Real-world Scenario
In a React component, you use const
for props and most functions, and let
inside loops or mutable form fields.
Arrow Functions (() => {})
🧠 Detailed Explanation
Arrow functions are a shorter way to write functions in JavaScript. They’re clean, modern, and used a lot in React.
Instead of writing a full function like this:
function sayHello(name) {
return "Hello, " + name;
}
You can write it like this:
const sayHello = (name) => "Hello, " + name;
🔑 Key things to know:
- Shorter: Less code, easier to read
- No
this
keyword: It uses the one from outside - Great for small functions, especially in React (like in map, filter, or event handlers)
Use arrow functions when you don’t need special keywords like this
, and when you want to keep your code short and clean.
💡 Examples
1. Basic Arrow Function (One Line)
// Traditional
function greet(name) {
return "Hello, " + name;
}
// Arrow version
const greet = (name) => "Hello, " + name;
console.log(greet("Ali")); // Hello, Ali
Why it’s useful: Cleaner and shorter to write small functions.
2. Arrow Function with No Parameters
const sayHi = () => "Hi there!";
console.log(sayHi()); // Hi there!
Note: Use empty ()
if your function doesn’t take any arguments.
3. Arrow Function with Multiple Statements
const getMessage = (name) => {
const greeting = "Welcome, ";
return greeting + name;
};
console.log(getMessage("Aisha")); // Welcome, Aisha
Tip: Use { }
and return
for longer logic.
4. Using Arrow Function with map()
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
Why it’s great: You don’t need function()
when looping or transforming arrays.
5. Arrow Function in React JSX
<button onClick={() => alert("Clicked!")}>
Click Me
</button>
Why: You can write event logic inline without creating a separate function.
🔁 Alternative Concepts
Instead of arrow functions, you can use traditional function expressions or declarations. Arrow functions are not suited for object methods or constructors because they do not have their own this
.
❓ General Questions & Answers
Q1: What is an arrow function in simple words?
A:
An arrow function is a shorter way to write a JavaScript function using the =>
symbol.
It makes your code cleaner, especially for small tasks like event handling or array methods.
// Traditional function
function greet(name) {
return "Hi " + name;
}
// Arrow function
const greet = (name) => "Hi " + name;
Q2: Why should I use arrow functions?
A: Use arrow functions because:
- They are short and easy to write ✍️
- They automatically use the surrounding
this
(good in React!) - They’re perfect for simple tasks
Q3: Are arrow functions better than traditional functions?
A:
Not always. They’re better for simple logic and callbacks.
But traditional functions are better when you need your own this
(like in classes or constructors).
Q4: Can arrow functions return objects?
A:
Yes, but you must wrap the object in ( )
to avoid confusion with function blocks.
const getUser = () => ({ name: "Ali", age: 25 });
console.log(getUser());
// { name: "Ali", age: 25 }
Q5: Can I use arrow functions with this
?
A:
Arrow functions don’t create their own this
.
They use the this
from where they were defined.
This is super useful in React components, because it avoids common bugs.
🛠️ Technical Questions & Answers
Q1: Do arrow functions have their own this
?
A: No, arrow functions don’t have their own this
.
They inherit this
from their surrounding (parent) scope.
// Example 1:
const obj = {
name: "Ali",
regularFunction: function () {
console.log("Regular:", this.name); // ✅ Ali
},
arrowFunction: () => {
console.log("Arrow:", this.name); // ❌ undefined
},
};
obj.regularFunction();
obj.arrowFunction();
Why it matters: Don’t use arrow functions inside objects if you need to access this
.
Q2: Can I use arguments
in an arrow function?
A: No. Arrow functions don’t have their own arguments
object.
function showArgs() {
console.log(arguments); // ✅ works
}
const arrowArgs = () => {
console.log(arguments); // ❌ ReferenceError
};
Solution: Use rest parameters instead:
const arrowArgs = (...args) => {
console.log(args); // ✅ works
};
arrowArgs(1, 2, 3); // [1, 2, 3]
Q3: Can arrow functions be used as constructors?
A: No. Arrow functions cannot be used with new
.
They don’t have a prototype
and will throw an error.
const User = (name) => {
this.name = name;
};
const u = new User("Ali"); // ❌ TypeError
Solution: Use a regular function if you need to create objects using new
.
Q4: Can arrow functions have default parameters?
A: Yes. Just like traditional functions, you can define default values.
const greet = (name = "Guest") => "Hello, " + name;
console.log(greet()); // Hello, Guest
console.log(greet("Ali")); // Hello, Ali
Q5: What happens when you return an object directly?
A: You must wrap the object in parentheses ()
or it will be confused with a function block.
const getUser = () => ({ name: "Ali", age: 25 }); // ✅
const brokenUser = () => { name: "Ali" }; // ❌ returns undefined
Why: JavaScript thinks the curly braces are the function’s body, not an object.
✅ Best Practices with Examples
1. Use Arrow Functions for Short, One-Liner Functions
Arrow functions make small functions cleaner and easier to read.
// ✅ Good
const double = (n) => n * 2;
// ❌ Longer than needed
function double(n) {
return n * 2;
}
2. Avoid Using Arrow Functions for Object Methods
They don’t have their own this
, so object references may not behave as expected.
const person = {
name: "Ali",
greet: function () {
console.log("Hi, I'm " + this.name); // ✅ Works
},
badGreet: () => {
console.log("Hi, I'm " + this.name); // ❌ undefined
},
};
person.greet();
person.badGreet();
3. Use Arrow Functions in Array Methods
Arrow functions are great inside map()
, filter()
, reduce()
, etc.
const numbers = [1, 2, 3, 4];
const squared = numbers.map(n => n * n);
console.log(squared); // [1, 4, 9, 16]
4. Use Parentheses to Return Objects
Without ( )
, JavaScript thinks you’re writing a code block.
const getUser = () => ({ name: "Ali", age: 25 }); // ✅
const brokenUser = () => { name: "Ali" }; // ❌ Returns undefined
5. Don’t Use Arrow Functions Where You Need this
Arrow functions don’t have their own this
, so they can cause unexpected behavior in class-based or event-driven code.
class Counter {
count = 0;
increment = () => {
this.count++;
console.log(this.count);
};
}
Tip: Arrow functions are helpful inside React components or classes to keep this
correctly scoped.
6. Keep Arrow Functions Readable
Use multiple lines with { }
when logic gets long.
// ✅ Clean one-liner
const add = (a, b) => a + b;
// ✅ Multi-line for longer logic
const formatUser = (name, age) => {
const prefix = "User:";
return `${prefix} ${name} (${age})`;
};
🌍 Real-World Scenario
In React, arrow functions are often used to pass event handlers or map over lists. They ensure this
is preserved correctly without having to manually bind it.
const TodoList = ({ todos }) => (
<ul>
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
);
Spread Operator (...)
🧠 Detailed Explanation
The spread operator in JavaScript is written as ...
. It means “take everything inside” an array or object and “spread” it out.
Think of it like unboxing a gift and laying out all the items one by one. 🎁➡️📦📦📦
It is used to:
- 📋 Copy arrays or objects
- 🔗 Combine multiple arrays or objects together
- 🔄 Update or replace properties in objects (especially in React state)
You use it like this:
// Copy an array
const original = [1, 2, 3];
const copy = [...original]; // [1, 2, 3]
// Add items to the array
const more = [...original, 4, 5]; // [1, 2, 3, 4, 5]
// Copy an object
const user = { name: "Ali" };
const newUser = { ...user, age: 25 }; // { name: "Ali", age: 25 }
💡 Summary: The spread operator is like saying “put everything from here into there.” It’s clean, powerful, and used a lot in React and modern JavaScript.
💡 Examples
1. Copy an Array
const numbers = [1, 2, 3];
// Using spread to make a copy
const copiedNumbers = [...numbers];
console.log(copiedNumbers); // [1, 2, 3]
Why? So you don’t change the original array accidentally.
2. Combine Arrays
const first = [1, 2];
const second = [3, 4];
// Merge into one
const combined = [...first, ...second];
console.log(combined); // [1, 2, 3, 4]
Why? Easy way to combine arrays without loops.
3. Add New Elements to an Array
const base = [2, 3, 4];
// Add 1 at the start and 5 at the end
const updated = [1, ...base, 5];
console.log(updated); // [1, 2, 3, 4, 5]
Why? Keep your original array safe and still build new versions of it.
4. Copy an Object
const user = { name: "Ali", age: 25 };
// Clone the user object
const clonedUser = { ...user };
console.log(clonedUser); // { name: "Ali", age: 25 }
Why? So that changes to the clone don’t affect the original.
5. Update/Add Properties to an Object
const user = { name: "Ali" };
// Add age to the user
const updatedUser = { ...user, age: 30 };
console.log(updatedUser); // { name: "Ali", age: 30 }
Why? This is a common React pattern for updating state.
6. Spread in Function Parameters (Rest Example)
const numbers = [10, 20, 30];
function sum(a, b, c) {
return a + b + c;
}
console.log(sum(...numbers)); // 60
Why? You can use spread to pass array items as individual arguments.
🔁 Alternative Concepts
Object.assign()
can also be used to copy or merge objects, but ...
is cleaner and more readable.
❓ General Questions & Answers
Q1: What is the spread operator in JavaScript?
A: The spread operator is written as ...
. It takes items from an array or object and spreads them into a new array or object. It’s often used to copy or merge data in a clean way.
const arr = [1, 2];
const copy = [...arr]; // copy is [1, 2]
Q2: What’s the difference between spread and rest parameters?
A: They look the same (...
), but:
- Spread is used to expand items out into a new array or object.
- Rest is used to gather multiple values into an array (inside a function).
// Spread
const arr1 = [1, 2];
const arr2 = [...arr1, 3]; // [1, 2, 3]
// Rest
function sum(...nums) {
return nums.reduce((a, b) => a + b);
}
sum(1, 2, 3); // 6
Q3: Can I use the spread operator on both arrays and objects?
A: Yes! That’s what makes it powerful. You can copy arrays, merge arrays, and also clone or update objects easily.
const obj1 = { a: 1 };
const obj2 = { b: 2 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2 }
Q4: Does spreading create a new copy of the data?
A: Yes, but only a shallow copy. That means top-level values are copied, but if there are nested objects, those are still shared (not deeply cloned).
const user = { name: "Ali", address: { city: "Lahore" } };
const clone = { ...user };
clone.address.city = "Karachi";
console.log(user.address.city); // 😱 Karachi (also changed)
Tip: For deep cloning, consider using libraries like lodash
or structured cloning.
Q5: Why is the spread operator so common in React?
A: Because React state must not be mutated directly. Spread helps to create a new version of the state with updated values, keeping the original safe.
const [user, setUser] = useState({ name: "Ali", age: 25 });
// Update age
setUser(prev => ({ ...prev, age: 26 }));
🛠️ Technical Questions & Answers
Q1: What happens if keys collide when spreading objects?
A: If you spread multiple objects with the same keys, the last one wins. It will overwrite the previous values.
const a = { name: "Ali", age: 25 };
const b = { age: 30 };
const merged = { ...a, ...b };
console.log(merged); // { name: "Ali", age: 30 }
Tip: Be careful when merging, especially with nested objects.
Q2: Can I use spread in function arguments?
A: Yes. It’s often used to spread an array into multiple parameters.
const values = [1, 2, 3];
function add(a, b, c) {
return a + b + c;
}
console.log(add(...values)); // 6
Why it’s useful: You can avoid manually accessing each element from the array.
Q3: Can I spread non-iterable values?
A: No. Only iterable types like arrays, strings, or sets can be spread. Objects are valid in object context.
const notIterable = 123;
// const error = [...notIterable]; // ❌ TypeError
Q4: How is spread different from Object.assign()?
A: Both can copy or merge objects. But spread is shorter and easier to read.
// Using spread
const newUser = { ...oldUser, age: 30 };
// Using Object.assign
const newUser = Object.assign({}, oldUser, { age: 30 });
Result: Both give the same result, but spread syntax is cleaner.
Q5: Is the spread operator deep or shallow?
A: It is shallow. Nested objects are not cloned, just referenced.
const user = {
name: "Ali",
contact: { email: "ali@example.com" }
};
const clone = { ...user };
clone.contact.email = "changed@example.com";
console.log(user.contact.email); // "changed@example.com" 😱
Lesson: Be careful when spreading objects with nested structures.
✅ Best Practices with Examples
1. Always Use Spread to Avoid Mutating the Original Array/Object
Mutating data directly can cause bugs, especially in React or Redux. Spread helps you create clean copies.
const numbers = [1, 2, 3];
// ❌ Avoid this
const copyWrong = numbers;
copyWrong.push(4); // Also changes `numbers`
// ✅ Do this instead
const copyRight = [...numbers, 4]; // New array, original is safe
2. Use Spread to Merge Arrays Cleanly
No need to use concat()
or loops when you can just spread them.
const a = [1, 2];
const b = [3, 4];
const merged = [...a, ...b]; // [1, 2, 3, 4]
3. Update Object State Without Mutation
This is a very common React pattern to keep the state immutable.
const [user, setUser] = useState({ name: "Ali", age: 25 });
// ✅ Safe way to update age
setUser(prev => ({ ...prev, age: 26 }));
4. Combine Default and Custom Props or Settings
You can spread default settings and override them with custom ones.
const defaultSettings = { theme: "light", layout: "grid" };
const userSettings = { theme: "dark" };
const final = { ...defaultSettings, ...userSettings };
// { theme: "dark", layout: "grid" }
5. Avoid Spread for Deep Cloning
Spread only does a shallow copy. Use other tools for deep copying nested objects.
const deepUser = {
name: "Ali",
profile: { bio: "Engineer" }
};
const shallowCopy = { ...deepUser };
shallowCopy.profile.bio = "Changed";
console.log(deepUser.profile.bio); // 😱 Changed (oops!)
Solution: Use structuredClone()
, lodash.cloneDeep()
, or JSON methods (with care).
🌍 Real-World Scenario
In React, the spread operator is often used to update state without mutation:
const [user, setUser] = useState({ name: "Ali", age: 20 });
const updateAge = () => {
setUser(prev => ({ ...prev, age: 21 }));
};
Destructuring (Objects & Arrays)
🧠 Detailed Explanation
Destructuring is a way to unpack values from arrays or get properties from objects — and save them into variables quickly and easily.
Instead of writing long code to access values one by one, you can grab multiple values in one line.
💡 Object Destructuring
When working with objects, you can pull out specific properties by name:
const user = { name: "Sara", age: 22 };
const { name, age } = user;
console.log(name); // "Sara"
console.log(age); // 22
💡 Array Destructuring
When working with arrays, you get values by position:
const colors = ["red", "blue", "green"];
const [first, second] = colors;
console.log(first); // "red"
console.log(second); // "blue"
✅ Destructuring saves time and makes your code shorter and cleaner — especially when working with components, props, or API responses.
💡 Examples
Example 1: Destructuring an Object
Let’s say you have a user object:
const user = {
name: "Areeba",
age: 24,
city: "Karachi"
};
const { name, age } = user;
console.log(name); // "Areeba"
console.log(age); // 24
Explanation: We directly pulled out the name
and age
values from the user
object without writing user.name
or user.age
.
Example 2: Destructuring an Array
const fruits = ["apple", "banana", "orange"];
const [first, second] = fruits;
console.log(first); // "apple"
console.log(second); // "banana"
Explanation: You get values based on their order in the array.
Example 3: Using Default Values
const user = { name: "Zara" };
const { name, country = "Pakistan" } = user;
console.log(name); // "Zara"
console.log(country); // "Pakistan"
Explanation: Since the object didn’t have a country
, we used a default fallback value.
Example 4: Renaming While Destructuring
const user = { name: "Ali" };
const { name: username } = user;
console.log(username); // "Ali"
Explanation: This is helpful when the original name might conflict with another variable in your code.
Example 5: Nested Object Destructuring
const person = {
contact: {
email: "user@example.com",
phone: "123456789"
}
};
const {
contact: { email, phone }
} = person;
console.log(email); // "user@example.com"
console.log(phone); // "123456789"
Explanation: You can destructure deep inside objects too!
🔁 Alternative Concepts
You could use traditional property access like user.name
, but destructuring is cleaner and reduces repetition.
❓ General Questions & Answers
Q1: Why should I use destructuring instead of traditional property access?
A: Destructuring helps make your code shorter and easier to read. Instead of accessing values like user.name
and user.age
every time, you can grab them all at once like this:
const { name, age } = user;
This saves time and makes your code cleaner — especially when working with objects that have many properties or with props in React components.
Q2: Can I use destructuring with arrays too?
A: Yes! Destructuring works with both objects and arrays. The main difference is:
- For objects, you destructure by property name.
- For arrays, you destructure by position/order.
// Object
const { name } = user;
// Array
const [first, second] = fruits;
Q3: What happens if the property doesn’t exist?
A: If the property you’re trying to destructure doesn’t exist, the variable will be undefined
. You can handle this using a default value:
const { age = 18 } = user;
This way, if age
is missing, it will default to 18
.
Q4: Can I rename variables while destructuring?
A: Absolutely! This is useful when you want a different variable name:
const { name: userName } = user;
console.log(userName); // not 'name'
Q5: Where is destructuring useful in real life?
A: You’ll use destructuring a lot in React (like with props
), when working with API responses, or when dealing with complex data. It’s a powerful way to write less repetitive code and keep things clean and readable.
🛠️ Technical Q&A with Examples
Q1: How do I destructure props in a React component?
A: You can destructure props directly inside the function parameter or inside the function body.
// ✅ Option 1: Destructure inside the function body
function Greeting(props) {
const { name } = props;
return <p>Hello, {name}!</p>;
}
// ✅ Option 2: Destructure in the parameter
function Greeting({ name }) {
return <p>Hello, {name}!</p>;
}
✅ Tip: Use Option 2 when you only need a few props — it keeps the code short and clean.
Q2: Can I skip values while destructuring arrays?
A: Yes. Use commas to skip elements in the array.
const numbers = [10, 20, 30];
const [ , second ] = numbers;
console.log(second); // 20
This is useful when you want only a specific item from the array.
Q3: How can I destructure nested objects?
A: You can destructure properties that are deeply nested like this:
const user = {
name: "Ali",
address: {
city: "Lahore",
zip: 54000
}
};
const { address: { city, zip } } = user;
console.log(city); // "Lahore"
console.log(zip); // 54000
Q4: What if I want to destructure but rename the variable?
A: Use a colon to assign a new name:
const user = { fullName: "Zara Ahmed" };
const { fullName: name } = user;
console.log(name); // "Zara Ahmed"
✅ Use Case: Prevent naming conflicts or improve readability.
Q5: Can I destructure function return values?
A: Yes — if a function returns an object or array, you can immediately unpack it.
// Example with object return
function getUser() {
return { name: "Imran", age: 30 };
}
const { name, age } = getUser();
// Example with array return
function getCoordinates() {
return [50, 100];
}
const [x, y] = getCoordinates();
✅ Tip: This is very common in API and utility functions.
✅ Best Practices with Examples
1. Destructure early for cleaner code
Pull values out at the top of the function instead of repeatedly accessing them later.
// ✅ Good
function showUserDetails(user) {
const { name, age } = user;
console.log(name, age);
}
// ❌ Avoid repetitive access
function showUserDetails(user) {
console.log(user.name);
console.log(user.age);
}
2. Rename when needed
This is helpful if the original name conflicts with existing variables or is unclear.
const product = { price: 100 };
const { price: productPrice } = product;
console.log(productPrice); // 100
3. Use default values to avoid undefined errors
const user = { name: "Sara" };
const { name, city = "Unknown" } = user;
console.log(city); // "Unknown"
This prevents runtime errors and makes code more robust.
4. Destructure arrays to clearly show intent
Destructuring makes it easy to extract values from ordered data like arrays.
const [first, second] = ["React", "Vue"];
console.log(first); // "React"
5. Destructure return values to write cleaner code
function getUser() {
return { name: "Aisha", role: "Admin" };
}
const { name, role } = getUser();
✅ Tip: Use this for clean extraction from helper utilities or API responses.
6. Destructure React props
// Instead of this:
function Profile(props) {
return <p>{props.name}</p>;
}
// Use this:
function Profile({ name }) {
return <p>{name}</p>;
}
🌍 Real-world Scenario
In React, you often destructure props directly in a component for cleaner syntax:
function Welcome({ name }) {
return <h1>Hello, {name}</h1>;
}
Ternary Operator (condition ? A : B)
🧠 Detailed Explanation
The ternary operator is a shortcut for writing an if-else
condition in a single line. It’s used when you want to decide between two values based on a condition.
The format is:
condition ? valueIfTrue : valueIfFalse
- 🔸 If the condition is
true
, it returns valueIfTrue - 🔸 If the condition is
false
, it returns valueIfFalse
✅ It’s called “ternary” because it uses three parts: the condition, the result if true, and the result if false.
✅ You’ll often see it used inside JSX in React to show or hide elements or to decide what to display based on a variable.
Think of it like asking a quick yes/no question in one line!
Example:
const age = 20;
const message = age >= 18 ? "You can vote" : "You cannot vote";
// message = "You can vote"
📘 This means if the age is 18 or older, we say “You can vote”, otherwise “You cannot vote”.
💡 Examples
Example 1: Basic Value Assignment
const isLoggedIn = true;
const greeting = isLoggedIn ? "Welcome back!" : "Please log in";
console.log(greeting); // Output: "Welcome back!"
✅ Explanation: Since isLoggedIn
is true
, it returns the first value: "Welcome back!"
.
Example 2: Ternary in JSX (React)
function Welcome({ user }) {
return (
<h2>
{user ? `Hello, ${user.name}` : "Hello, Guest"}
</h2>
);
}
✅ Explanation: If the user
object exists, we show their name. If not, we display “Guest”.
Example 3: Nested Ternary (use carefully)
const score = 85;
const grade = score >= 90 ? "A"
: score >= 80 ? "B"
: score >= 70 ? "C"
: "Fail";
console.log(grade); // Output: "B"
⚠️ Explanation: This example shows how you can stack ternary operators. But be careful — it can be hard to read. Use this only for simple flows.
Example 4: Inline Styling (Conditional)
const isDarkMode = true;
const buttonStyle = {
backgroundColor: isDarkMode ? "#333" : "#fff",
color: isDarkMode ? "#fff" : "#000"
};
🎨 Explanation: Based on the theme, the button color changes. This is a clean way to apply dynamic styling.
Example 5: Conditional Button Label
const isSubmitting = false;
return (
<button>{isSubmitting ? "Submitting..." : "Submit"}</button>
);
✅ Explanation: The button shows “Submitting…” only when isSubmitting
is true
.
🔁 Alternatives
Instead of a ternary operator, you can use traditional if/else:
let message;
if (isLoggedIn) {
message = "Welcome";
} else {
message = "Log in";
}
🔸 Use ternary for simple conditions, and if/else for more complex logic blocks.
❓ General Questions & Answers
Q1: What is the ternary operator used for?
A: The ternary operator is a shorthand way to write simple if-else conditions. Instead of writing:
if (isOnline) {
status = "Online";
} else {
status = "Offline";
}
You can write:
const status = isOnline ? "Online" : "Offline";
✅ It saves space and keeps the code more readable for quick decisions.
Q2: Can I use the ternary operator inside JSX?
A: Yes! It’s very common in React to conditionally render UI elements. Example:
{user ? <p>Welcome, {user.name}</p> : <p>Please log in</p>}
This renders different messages based on whether user
exists.
Q3: Is the ternary operator only for assigning values?
A: No — you can use it in any expression, such as rendering UI, returning values, or assigning variables.
Q4: Can I chain multiple ternary operators together?
A: Yes, but it’s usually discouraged unless absolutely necessary. Chained ternaries can get confusing quickly. Example:
const status = score >= 90 ? "A"
: score >= 80 ? "B"
: score >= 70 ? "C"
: "Fail";
⚠️ Always prioritize readability.
Q5: Is it okay to use ternary operators inside return statements?
A: Yes, especially in React. It allows concise conditional rendering:
return isAdmin ? <AdminPanel /> : <UserPanel />;
This makes the UI logic more declarative and cleaner.
🛠️ Technical Q&A with Examples
Q1: Can I execute a function inside a ternary operator?
A: Yes, you can run functions inside both the true and false branches of a ternary.
function logStatus(isAdmin) {
isAdmin ? grantAccess() : denyAccess();
function grantAccess() {
console.log("Access granted");
}
function denyAccess() {
console.log("Access denied");
}
}
✅ Tip: Only use this for simple calls; for complex logic, use if-else blocks.
Q2: What happens if I don’t include the else part?
A: The ternary operator always expects both true
and false
outcomes. Omitting one results in a syntax error.
// ❌ Incorrect
condition ? doSomething(); // Incomplete
// ✅ Correct
condition ? doSomething() : null;
You can return null
or any fallback if you want to skip doing anything.
Q3: How does it work with multiple return values?
A: You can return different components or blocks conditionally using ternaries:
function Result({ passed }) {
return passed
? <SuccessMessage />
: <FailureMessage />;
}
This keeps return statements clean and declarative.
Q4: Can I store a ternary result inside an object?
A: Yes, it works perfectly inside objects and arrays.
const theme = {
mode: isDarkMode ? "dark" : "light",
background: isDarkMode ? "#111" : "#fff"
};
Useful for dynamic theming or settings.
Q5: Can I nest ternary operators inside JSX?
A: Technically yes, but use with caution to keep code readable.
return (
condition1
? <Component1 />
: condition2
? <Component2 />
: <Component3 />
);
✅ Recommendation: Extract logic into helper functions or variables for clarity.
✅ Best Practices with Examples
1. Use for simple conditions only
✅ Ternary operators are great for small, quick decisions in code.
// ✅ Good
const message = isLoggedIn ? "Welcome back!" : "Please log in.";
// ❌ Bad – too complex
const msg = a > b ? (x > y ? "Case A" : "Case B") : (z > w ? "Case C" : "Case D");
❌ This gets hard to read — use if-else
for complex logic.
2. Wrap JSX ternaries in parentheses
✅ Keeps your JSX readable and avoids accidental layout issues.
return (
isAdmin ? <AdminPanel /> : <UserPanel />
);
3. Avoid inline functions inside ternaries in performance-critical components
// ✅ Better for performance:
const renderPanel = isAdmin ? renderAdmin : renderUser;
return <div>{renderPanel()}</div>;
4. Ternary is great for applying dynamic styles or classes
<div className={isActive ? "box active" : "box"}>Item</div>
5. Use null
if no UI should be rendered in one case
{showWarning ? <p>Warning!</p> : null}
6. Prefer meaningful variable names for ternary results
// ✅ Clear and descriptive
const userRole = isAdmin ? "admin" : "user";
// ❌ Vague
const x = y ? "a" : "b";
7. Extract nested ternary logic into helper variables
// ❌ Bad
const label = score >= 90 ? "A" : score >= 80 ? "B" : "C";
// ✅ Good
let label;
if (score >= 90) label = "A";
else if (score >= 80) label = "B";
else label = "C";
🌍 Real-world Scenario
In a React e-commerce app, use a ternary to show stock status:
<p>{product.inStock ? "In Stock" : "Out of Stock"}</p>
Optional Chaining (?.)
🧠 Detailed Explanation
Optional Chaining is a way to safely access deeply nested properties in an object without causing an error if something is missing.
Normally in JavaScript, if you try to access a property that doesn’t exist, you get an error. Optional chaining lets you ask: “If this exists, then go ahead — if not, just return undefined
.”
It uses a special symbol: ?.
For example:
const user = {
name: "Ali",
profile: {
age: 25
}
};
console.log(user?.profile?.age); // 25 ✅
console.log(user?.contact?.phone); // undefined ✅ (no error)
🔥 So if contact
doesn’t exist, JavaScript won’t crash. It just gives you undefined
.
When to use: When you’re not sure if a value exists and want to avoid crashes.
💡 Examples
Example 1: Accessing nested properties safely
const user = {
name: "Ali",
profile: {
city: "Lahore"
}
};
console.log(user.profile.city); // ✅ Lahore
console.log(user?.profile?.city); // ✅ Lahore
console.log(user?.contact?.phone); // ✅ undefined (doesn’t crash)
Explanation: If contact
doesn’t exist, user.contact.phone
would crash. But user?.contact?.phone
returns undefined
safely.
Example 2: Optional chaining with function calls
const app = {
log: () => console.log("Logging...")
};
app.log?.(); // ✅ Logs "Logging..."
app.alert?.(); // ✅ Does nothing (no error)
Explanation: If the function exists, it runs. If not, it’s safely ignored.
Example 3: Optional chaining in arrays
const users = [{ name: "Zara" }, null];
console.log(users[0]?.name); // ✅ Zara
console.log(users[1]?.name); // ✅ undefined
Explanation: Without optional chaining, accessing users[1].name
would throw an error because users[1]
is null.
Example 4: Combined with Nullish Coalescing
const person = {
contact: null
};
const phone = person?.contact?.phone ?? "Not Available";
console.log(phone); // ✅ "Not Available"
Explanation: If phone
is undefined, fallback text is used with ??
.
🔁 Alternative Methods
- Manual Checks: Using
&&
chaining likeobj && obj.prop
- try…catch: Wrapping access in try-catch for safety
- Lodash get:
_.get(obj, 'prop.deep.key', defaultValue)
❓ General Questions & Answers
Q1: What is optional chaining used for?
A: Optional chaining is used to safely access nested object properties without throwing an error if something doesn’t exist. Instead of crashing the app, it returns undefined
.
const data = {};
console.log(data.user.name); // ❌ Error: Cannot read 'name'
console.log(data.user?.name); // ✅ undefined (no error)
Q2: What happens if I use optional chaining on something that does exist?
A: It behaves just like normal access. If the property exists, it returns the value.
const user = { name: "Ali" };
console.log(user?.name); // ✅ "Ali"
Q3: Can I use optional chaining on arrays?
A: Yes! It’s helpful when you’re unsure if an item at a certain index exists.
const items = ["apple"];
console.log(items[1]?.toUpperCase()); // ✅ undefined (instead of error)
Q4: Is optional chaining supported in all browsers?
A: It’s supported in all modern browsers (Chrome, Edge, Firefox, Safari). However, older browsers like Internet Explorer do not support it.
Q5: Is optional chaining the same as using &&
?
A: They are similar but not the same. &&
checks for truthy values, while ?.
checks specifically for null
or undefined
.
// Optional chaining:
obj?.value
// Logical AND:
obj && obj.value
Key Difference: Optional chaining will still access properties if the value is falsy (like 0 or “”). &&
may skip them.
🛠️ Technical Q&A with Examples
Q1: How does optional chaining prevent runtime errors?
A: It short-circuits if a property in the chain is null
or undefined
, returning undefined
instead of throwing an error.
const response = null;
console.log(response?.user?.email); // ✅ undefined, no error
Q2: Can optional chaining be used with function calls?
A: Yes, if the function might not exist, you can safely attempt to call it using ?.()
.
const utils = {
sayHi: () => console.log("Hi")
};
utils.sayHi?.(); // ✅ "Hi"
utils.sayBye?.(); // ✅ nothing happens
Q3: Can I use optional chaining with arrays and dynamic keys?
A: Yes! You can use it with indexed arrays and dynamic properties.
const users = [{ name: "Ali" }, null];
console.log(users[1]?.name); // ✅ undefined
const key = "name";
console.log(users[0]?.[key]); // ✅ "Ali"
Q4: What happens if I use optional chaining on a non-existent variable?
A: You’ll still get a ReferenceError. Optional chaining only works on defined variables.
console.log(person?.name); // ❌ ReferenceError: person is not defined
✅ Solution: Always ensure the base object exists before using ?.
.
Q5: Can I combine optional chaining with nullish coalescing?
A: Yes! You can use ?.
with ??
to provide fallback values.
const user = {};
const username = user?.info?.name ?? "Guest";
console.log(username); // ✅ "Guest"
✅ Best Practices with Examples
1. Use optional chaining when you’re not sure if a property exists
✅ Prevents your code from crashing when accessing nested values from APIs or optional fields.
const userData = apiResponse?.user?.profile;
2. Combine with ??
to set default values
✅ If the value is null
or undefined
, fall back to something safe.
const username = user?.info?.name ?? "Guest";
3. Don’t use optional chaining on a variable that’s not declared
⚠️ You’ll get a ReferenceError
if the variable itself isn’t defined.
console.log(user?.name); // ❌ ReferenceError if `user` is not declared
✅ Solution: Ensure the variable is at least declared first.
4. Avoid deeply nested optional chaining unless necessary
🔸 Too much optional chaining can make your code harder to debug. Use with care.
// ⚠️ Hard to debug:
user?.details?.info?.meta?.something?.else
// ✅ Better:
const info = user?.details?.info;
if (info) {
// work with info
}
5. Use in conditional rendering to avoid crashes in UI
✅ Very useful in React apps when rendering dynamic content from props or API data.
<p>Name: {user?.profile?.name}</p>
<img src={user?.avatar?.url} alt="Profile" />
🌍 Real-world Scenario
In a blog app, you may want to access a post’s author bio like post?.author?.bio
. If the author is missing, the app won’t break — it will just show nothing.
Nullish Coalescing (??)
🧠 Detailed Explanation
The ??
operator is called the Nullish Coalescing Operator. It helps you set a default value when a variable is either null
or undefined
.
It’s like saying: “If the first value is missing (null or undefined), then use the second value.”
Why it’s useful:
- ✅ It doesn’t treat
0
or""
as missing. - ✅ It only checks if a value is
null
orundefined
.
Example:
let name = null;
let displayName = name ?? "Guest";
console.log(displayName); // Guest
In this case, name
is null
, so "Guest"
is used instead.
Another Example:
let count = 0;
let total = count ?? 10;
console.log(total); // 0
Here, count
is 0 (which is valid), so it doesn’t fall back to 10 — unlike ||
which would’ve done that.
💡 Examples
Example 1: Basic Fallback
let username = null;
let displayName = username ?? "Anonymous";
console.log(displayName); // Output: Anonymous
Explanation: Since username
is null
, it falls back to “Anonymous”.
Example 2: No fallback needed
let theme = "dark";
let activeTheme = theme ?? "light";
console.log(activeTheme); // Output: dark
Explanation: theme
has a value, so fallback is not used.
Example 3: Difference from ||
(OR operator)
let score = 0;
let result1 = score || 100;
let result2 = score ?? 100;
console.log(result1); // Output: 100
console.log(result2); // Output: 0
Explanation: ||
treats 0 as “falsy”, so it falls back to 100.
But ??
only checks for null
or undefined
, so it keeps the 0.
Example 4: Function with default parameter
function greet(userName) {
let nameToShow = userName ?? "Guest";
console.log("Hello, " + nameToShow + "!");
}
greet("Sara"); // Hello, Sara!
greet(null); // Hello, Guest!
greet(undefined); // Hello, Guest!
Explanation: You can use ??
inside functions to make them smarter with defaults.
🔁 Alternative Concepts
||
can be used for default values but may give incorrect results with falsy values.
let value = input || "Default"; // treats "", 0, false as "missing"
let valueSafe = input ?? "Default"; // only null or undefined triggers fallback
❓ General Questions & Answers
Q1: What is the nullish coalescing operator?
A: The ??
operator is used to provide a default value **only when** the left-hand side is null
or undefined
. It’s a cleaner alternative to ||
when you don’t want to treat 0
, ''
, or false
as “empty”.
Q2: How is ??
different from ||
?
A: ||
returns the right-hand side if the left-hand side is “falsy” (e.g., 0
, false
, ''
, null
, undefined
).
??
only returns the right-hand side **if** the left-hand side is null
or undefined
.
const value = 0;
console.log(value || 100); // 100 (because 0 is falsy)
console.log(value ?? 100); // 0 (because 0 is not null/undefined)
Q3: Can I use ??
in place of ||
?
A: Not always. Use ??
only when you specifically want to check for null
or undefined
, and still accept values like 0
, false
, or ''
as valid.
Q4: When should I use nullish coalescing?
A: Use it when you want to provide a default value **only if** the variable is truly missing or uninitialized — for example, when fetching optional config values, user data, or form inputs.
Q5: Is ??
supported in all browsers?
A: It’s supported in all modern browsers (Chrome 80+, Firefox 72+, Safari 13.1+, Edge 80+). For older environments, a transpiler like Babel can convert it to supported code.
🛠️ Technical Q&A with Examples
Q1: How do I safely provide a fallback value for a possibly undefined user object?
A: Use ??
to provide a default when the value is null
or undefined
.
const user = undefined;
const username = user ?? "Guest";
console.log(username); // Output: Guest
Q2: What happens if I use ||
instead of ??
for a boolean or numeric field?
A: You may unintentionally override valid values like false
or 0
.
const isAdmin = false;
const resultWithOr = isAdmin || true;
const resultWithNullish = isAdmin ?? true;
console.log(resultWithOr); // true ❌ (wrong override)
console.log(resultWithNullish); // false ✅
Q3: How do I combine ??
with other logic like string defaults?
A: Use ??
just like ||
, but keep in mind precedence rules — wrap conditions in parentheses if needed.
const greeting = (userInput ?? "Hello") + ", welcome!";
console.log(greeting); // Hello, welcome!
Q4: Can I chain ??
to check multiple fallback options?
A: Yes. It’s common when checking multiple sources.
const a = null;
const b = undefined;
const c = "Final fallback";
const result = a ?? b ?? c;
console.log(result); // Final fallback
Q5: What error might occur if I combine ||
and ??
without parentheses?
A: It will throw a syntax error due to ambiguous parsing. JS doesn’t allow mixing them directly.
// ❌ SyntaxError
// let result = null || undefined ?? "default";
// ✅ Correct with parentheses:
let result = (null || undefined) ?? "default";
✅ Best Practices with Examples
1. Use ??
when you specifically want to check for null
or undefined
This avoids replacing valid values like 0
, false
, or ''
.
const count = 0;
const safeCount = count ?? 10;
console.log(safeCount); // 0 ✅
2. Avoid using ||
if 0, false
, or ""
are acceptable values
||
treats those values as falsy, and may incorrectly override them.
const input = "";
const resultOr = input || "Default";
const resultNullish = input ?? "Default";
console.log(resultOr); // "Default" ❌
console.log(resultNullish); // "" ✅
3. Use parentheses when combining ??
with ||
or &&
This avoids syntax errors and improves clarity.
// ❌ let value = undefined || null ?? "fallback"; // SyntaxError
// ✅ Correct
let value = (undefined || null) ?? "fallback";
4. Use ??
in function arguments for safer defaults
This ensures only null
or undefined
get the default.
function greet(name) {
const displayName = name ?? "Guest";
console.log("Hello, " + displayName);
}
greet(undefined); // Hello, Guest
greet(""); // Hello, ✅ (empty string preserved)
5. Combine ?.
and ??
for safe access + fallback
When accessing deeply nested data safely:
const user = { profile: null };
const username = user.profile?.name ?? "Anonymous";
console.log(username); // "Anonymous"
🌍 Real-World Scenario
In a form input, if the user hasn’t typed anything yet, but you want to show a fallback value unless the user types something valid (including ""
), ??
is the best fit:
let userInput = "";
let display = userInput ?? "Enter your name";
console.log(display); // ""
Template Literals (${}
)
🧠 Detailed Explanation
Template literals are a modern way in JavaScript to write strings using backticks (`
) instead of quotes ('
or "
).
The biggest benefit is that you can insert variables or run JavaScript code inside your string using ${}
.
- 🔹 Use backticks instead of quotes.
- 🔹 Insert variables easily with
${}
. - 🔹 Write multi-line strings without using
\n
.
This makes your code cleaner, shorter, and easier to read.
Instead of writing:
"Hello, " + name + "!"
You can write:
`Hello, ${name}!`
💡 Examples
Example 1: Basic Variable Insertion
const name = "Ali";
console.log(`Hello, ${name}!`);
Output: Hello, Ali!
Explanation: The value of the name
variable is directly inserted inside the string.
Example 2: Multi-line Strings
const message = `Hello,
This is a multi-line
string.`;
console.log(message);
Output:
Hello, This is a multi-line string.
Explanation: No need to use \n
. Just press enter inside backticks.
Example 3: Expression Inside Template
const x = 5;
const y = 10;
console.log(`Sum: ${x + y}`);
Output: Sum: 15
Explanation: You can run JavaScript expressions inside ${}
.
Example 4: Using Functions
function greet(name) {
return `Hello, ${name.toUpperCase()}!`;
}
console.log(greet("fatima"));
Output: Hello, FATIMA!
Explanation: You can even call functions inside template literals.
Example 5: Embedding HTML
const title = "Welcome";
const html = `
<div class="header">
<h1>${title}</h1>
</div>
`;
document.body.innerHTML = html;
Explanation: Template literals are perfect for generating HTML dynamically in JavaScript.
🔁 Alternative Methods
Before ES6, you would concatenate like this:
const greeting = "Hello, " + name + "!";
Template literals simplify this syntax significantly.
❓ General Questions & Answers
Q1: What is a Template Literal?
A: A Template Literal is a way to create strings in JavaScript using backticks (`
) instead of quotes. It lets you embed variables or expressions using ${}
.
const name = "Sara";
console.log(`Hello, ${name}`); // Hello, Sara
Q2: Why use backticks instead of quotes?
A: Backticks allow:
- 👉 Multi-line strings without
\n
- 👉 Embedding values or expressions with
${}
- 👉 Cleaner and more readable string formatting
Q3: Can I use functions or logic inside ${}
?
A: Yes! You can run any valid JavaScript expression inside ${}
.
const price = 10;
const tax = 0.2;
console.log(`Total: $${price + (price * tax)}`); // Total: $12
Q4: Can I use template literals to generate HTML?
A: Yes. Template literals are great for creating dynamic HTML in JS.
const title = "Welcome";
const html = `${title}
`;
document.body.innerHTML = html;
Q5: What happens if I forget to use backticks?
A: If you use single (' '
) or double (" "
) quotes instead of backticks, ${}
will not work — the variable won’t be replaced.
// ❌ Incorrect
console.log("Hello, ${name}"); // Output: Hello, ${name}
// ✅ Correct
console.log(`Hello, ${name}`); // Output: Hello, Sara
🛠️ Technical Q&A with Examples
Q1: How can I include expressions inside a template literal?
A: Use ${expression}
inside backticks to evaluate the expression inline.
const a = 5;
const b = 3;
console.log(`The sum is ${a + b}`); // Output: The sum is 8
Q2: How do I write multi-line strings?
A: Template literals preserve line breaks automatically without using \n
.
const message = `
Hello!
This is a multi-line
string example.
`;
console.log(message);
Q3: Can I use functions inside template literals?
A: Yes, you can call functions or run logic directly in ${}
.
function greet(name) {
return `Welcome, ${name.toUpperCase()}!`;
}
console.log(greet("john")); // Output: Welcome, JOHN!
Q4: How do I generate HTML snippets using template literals?
A: Use backticks to create HTML blocks with dynamic data.
const user = "Ali";
const html = `
<div class="card">
<h2>Hello, ${user}</h2>
<p>Welcome back!</p>
</div>
`;
document.body.innerHTML = html;
Q5: Can I nest template literals?
A: Yes. You can combine them using functions or logic.
const fruit = "apple";
const count = 3;
const message = `You have ${count} ${count > 1 ? fruit + "s" : fruit}`;
console.log(message); // Output: You have 3 apples
✅ Best Practices with Examples
1. Use template literals instead of + for cleaner code
✅ This improves readability, especially with dynamic values.
// ✅ Good
const name = "Sara";
const greeting = `Hello, ${name}!`;
// ❌ Bad
const greeting = "Hello, " + name + "!";
2. Use backticks to write multiline strings easily
✅ Avoids clutter and line break hacks like \n
.
const multiline = `
Line 1
Line 2
Line 3
`;
3. Use expressions inside ${}
✅ You can run inline logic like math or function calls.
const total = 120;
const discount = 20;
const final = `Final Price: $${total - discount}`;
4. Don’t write complex logic inside template expressions
✅ Extract it into variables first to stay clean.
// ✅ Good
const tax = 10;
const price = 100;
const total = price + tax;
const summary = `Total: $${total}`;
// ❌ Avoid this
const summary = `Total: $${100 + 10}`;
5. Escape backticks if they need to appear inside the string
✅ Use \`
to show backticks inside the template literal.
const str = `You can write backticks like this: \``;
6. Use template literals for small dynamic HTML chunks
✅ Great for rendering strings into the DOM when using vanilla JS.
const user = "Aisha";
document.body.innerHTML = `
<h1>Hello, ${user}</h1>
`;
🌍 Real-World Scenario
In a React app, you might construct dynamic class names or URLs using template literals:
const imageUrl = `https://api.site.com/img/${userId}.jpg`;
const className = `btn btn-${type}`;
Array .map()
Method
🧠 Detailed Explanation
The .map()
method is a built-in JavaScript function used on arrays. It helps you go through each item in the array and build a brand new array with updated values.
Think of it like this: you have a list of items, and you want to “transform” or change each one in some way. Instead of using a loop, .map()
makes it easier and cleaner.
- 🔹 It loops through every item in the array.
- 🔹 It runs a function on each item.
- 🔹 It creates a new array with the result of that function.
Important: The original array is not changed!
Example: If you have numbers like [1, 2, 3]
and you want to double each one, map()
gives you [2, 4, 6]
.
💡 Examples
Example 1: Double Numbers
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // Output: [2, 4, 6]
Explanation: Each number is multiplied by 2 using the map()
method.
Example 2: Convert Strings to Uppercase
const names = ['ali', 'sara', 'john'];
const uppercased = names.map(name => name.toUpperCase());
console.log(uppercased); // Output: ['ALI', 'SARA', 'JOHN']
Explanation: Each name is converted to uppercase using toUpperCase()
.
Example 3: Extract Specific Properties from Objects
const users = [
{ id: 1, name: 'Ali' },
{ id: 2, name: 'Sara' }
];
const names = users.map(user => user.name);
console.log(names); // Output: ['Ali', 'Sara']
Explanation: We extract just the name
property from each user object.
Example 4: Use in React for Rendering a List
const products = [
{ id: 1, name: 'Laptop' },
{ id: 2, name: 'Phone' }
];
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
Explanation: This is how map()
is often used in React to show a list of elements dynamically.
Example 5: Add Index to Each Item
const items = ['apple', 'banana'];
const indexed = items.map((item, index) => `${index + 1}. ${item}`);
console.log(indexed); // Output: ['1. apple', '2. banana']
Explanation: You can also access the index of each item and include it in your transformation.
🔁 Alternatives
forEach()
– Similar but does not return a new array.filter()
– Returns a subset based on condition.reduce()
– Transforms an array into a single value.
❓ General Questions & Answers
Q1: What is .map()
used for?
A: .map()
is used to take an array, change each item in some way, and return a new array with the updated items.
For example, turning lowercase names into uppercase:
const names = ['a', 'b'];
const upper = names.map(n => n.toUpperCase()); // ['A', 'B']
Q2: Does .map()
modify the original array?
A: No. It creates and returns a new array. The original one stays the same.
Q3: What happens if I don’t return a value inside .map()
?
A: You’ll get undefined
for each item in the new array.
Always remember to return something from your callback function.
// ❌ Missing return
const result = [1, 2].map(num => {
num * 2;
});
console.log(result); // [undefined, undefined]
// ✅ With return
const result = [1, 2].map(num => {
return num * 2;
});
console.log(result); // [2, 4]
Q4: Can I use .map()
on empty arrays?
A: You can, but it won’t do anything because there are no items to loop through.
Q5: Is .map()
better than forEach()
?
A: It depends on your goal:
map()
– Use it when you need a new array based on the original.forEach()
– Use it when you just want to run something (like logging or updating the UI) but don’t need a new array.
🛠️ Technical Q&A with Examples
Q1: How do I use both the item and its index in .map()
?
A: The second parameter of the callback gives you the index.
const fruits = ['apple', 'banana'];
const withIndex = fruits.map((fruit, index) => `${index + 1}. ${fruit}`);
console.log(withIndex); // ['1. apple', '2. banana']
Q2: What if I want to use async/await
with .map()
?
A: Use Promise.all()
to handle asynchronous operations inside .map()
.
const ids = [1, 2, 3];
const fetchUser = async (id) => {
return { id, name: `User ${id}` };
};
const users = await Promise.all(
ids.map(async id => await fetchUser(id))
);
console.log(users);
// Output: [{id: 1, name: 'User 1'}, {id: 2, ...}]
Q3: Can I conditionally return inside .map()
?
A: Yes, but make sure you still return something for every item (even if it’s null
).
const numbers = [1, 2, 3, 4];
const evenOnly = numbers.map(n => n % 2 === 0 ? n : null);
console.log(evenOnly); // [null, 2, null, 4]
Q4: What’s the difference between .map()
and .filter()
?
A: .map()
transforms each item and returns the same number of items. .filter()
removes some items based on a condition.
// map
[1, 2, 3].map(n => n * 2); // [2, 4, 6]
// filter
[1, 2, 3].filter(n => n > 1); // [2, 3]
Q5: Can I nest .map()
inside another .map()
?
A: Yes, especially for working with arrays inside arrays.
const matrix = [
[1, 2],
[3, 4]
];
const doubled = matrix.map(row => row.map(num => num * 2));
console.log(doubled); // [[2, 4], [6, 8]]
✅ Best Practices with Examples
1. Always return a value from the map callback
✅ If you forget to return, you’ll get undefined
for every item.
// ✅ Correct
const doubled = [1, 2, 3].map(num => num * 2);
// ❌ Wrong (missing return in block form)
const broken = [1, 2, 3].map(num => {
num * 2; // no return!
});
2. Use .map()
only when you need to build a new array
✅ If you just want to do something for each item (like logging), use .forEach()
instead.
[1, 2, 3].forEach(num => console.log(num)); // ✅ use forEach
const doubled = [1, 2, 3].map(num => num * 2); // ✅ use map when returning new array
3. Avoid writing too much logic inside the map function
✅ Extract it to a separate function if needed for better readability.
// ✅ Better readability
const formatUser = user => `${user.name} (${user.age})`;
const output = users.map(formatUser);
4. In React, always use a unique key
when mapping components
✅ This helps React keep track of elements during re-renders.
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
5. Be careful if the array might be null
or undefined
const users = null;
const names = (users || []).map(user => user.name); // ✅ fallback to empty array
6. You can chain .map()
with .filter()
or .reduce()
for complex tasks
const activeUsers = users
.filter(user => user.active)
.map(user => user.name.toUpperCase());
🌍 Real-world Scenario
When building a product list in an e-commerce React app, use map()
to iterate through product data and render components like cards or rows.
Array .filter()
& .find()
🧠 Detailed Explanation
Both .filter()
and .find()
are JavaScript array methods used to search for items based on a condition.
- 🔍
.filter()
goes through the entire array and returns all items that match the condition in a new array. - 🎯
.find()
goes through the array and returns only the first item that matches the condition.
✅ Use filter()
when you expect multiple results.
✅ Use find()
when you’re just looking for one item.
Both methods use a callback function — a function that runs for each item in the array to check if it matches your condition.
Example:
Let’s say you have a list of users, and you want to:
- Get all active users → use
filter()
- Find the user with ID 3 → use
find()
💡 Examples
Example 1: Using .filter()
to get active users
const users = [
{ id: 1, name: "Ali", active: true },
{ id: 2, name: "Sara", active: false },
{ id: 3, name: "John", active: true }
];
const activeUsers = users.filter(user => user.active);
console.log(activeUsers);
// Output:
// [
// { id: 1, name: "Ali", active: true },
// { id: 3, name: "John", active: true }
// ]
Explanation: The filter()
method checks each user. If active
is true
, it includes that user in the new array.
Example 2: Using .find()
to get a user by ID
const user = users.find(user => user.id === 2);
console.log(user);
// Output:
// { id: 2, name: "Sara", active: false }
Explanation: find()
stops as soon as it finds the first match — in this case, the user with ID 2.
Example 3: Filter even numbers
const numbers = [1, 2, 3, 4, 5, 6];
const even = numbers.filter(num => num % 2 === 0);
console.log(even); // [2, 4, 6]
Example 4: Find first long word
const words = ['cat', 'elephant', 'dog', 'tiger'];
const longWord = words.find(word => word.length > 5);
console.log(longWord); // 'elephant'
Explanation: It finds the first word where the length is greater than 5 and returns it immediately.
🔁 Alternatives or Related
some()
– returns true if at least one item matchesevery()
– returns true if all items matchmap()
– used to transform all items
❓ General Questions & Answers
Q1: What’s the difference between filter()
and find()
?
A:
filter()
returns all matching items in a new array.
find()
returns only the first matching item (not inside an array).
Example:
If you have 3 active users and use filter()
, it will give you all 3.
If you use find()
, it will give you just the first one it sees.
Q2: Can I use filter()
or find()
on a string?
A: No. These methods only work on arrays. If you want to use them on text, you’ll need to convert it to an array (e.g. using split()
).
Q3: What happens if no item matches the condition?
A:
filter()
will return an empty array[]
.find()
will returnundefined
.
Q4: Can I use these methods on arrays of objects?
A: Yes! That’s one of the most common use cases. You can check object properties like user.active
or user.id === 2
easily using filter()
and find()
.
Q5: Do these methods change the original array?
A: No. Both filter()
and find()
create a new result. The original array stays the same.
🛠️ Technical Q&A with Examples
Q1: How do I use the index in filter()
or find()
?
A: The callback function can take two arguments: the item and its index.
const numbers = [10, 20, 30, 40];
const filtered = numbers.filter((num, index) => index % 2 === 0);
console.log(filtered); // [10, 30]
Explanation: This filters every second item by using the index.
Q2: How can I combine filter()
with map()
?
A: You can chain them: first filter the items, then transform them with map.
const users = [
{ id: 1, active: true, name: 'Ali' },
{ id: 2, active: false, name: 'Sara' },
{ id: 3, active: true, name: 'John' }
];
const names = users
.filter(user => user.active)
.map(user => user.name);
console.log(names); // ['Ali', 'John']
Q3: What if I want to find an object inside an array using multiple conditions?
A: You can add more conditions inside your callback.
const items = [
{ id: 1, name: 'Item A', stock: 0 },
{ id: 2, name: 'Item B', stock: 5 }
];
const inStock = items.find(item => item.name === 'Item B' && item.stock > 0);
console.log(inStock); // { id: 2, name: 'Item B', stock: 5 }
Q4: What’s the performance difference between filter()
and find()
?
A: find()
is usually faster because it stops as soon as it finds the first match. filter()
checks the entire array no matter what.
Q5: Can I use filter()
or find()
on nested arrays?
A: Yes, you can. But you’ll likely need to use them inside another loop or method like map()
.
const nested = [
[1, 2],
[3, 4]
];
const even = nested.map(arr => arr.filter(n => n % 2 === 0));
console.log(even); // [[2], [4]]
✅ Best Practices with Examples
1. Use find()
when you only need one item
✅ It’s more efficient because it stops as soon as it finds a match.
const user = users.find(u => u.id === 3);
2. Use filter()
for multiple results
✅ Use it when you want a list of items that meet a condition.
const activeUsers = users.filter(u => u.active);
3. Always check the result of find()
before using it
⚠️ If no item is found, it returns undefined
which may break your code.
const user = users.find(u => u.id === 100);
if (user) {
console.log(user.name);
}
4. Use arrow functions for cleaner callbacks
✅ It’s concise and readable.
const even = [1, 2, 3, 4].filter(n => n % 2 === 0);
5. Chain with map()
for transformations
✅ Use this pattern to first select items, then transform them.
const names = users
.filter(u => u.active)
.map(u => u.name);
6. Don’t use filter()
if you expect only one match
❌ It’s inefficient and less readable. Use find()
instead.
// ✅ Better
const user = users.find(u => u.email === "test@example.com");
// ❌ Unnecessary
const result = users.filter(u => u.email === "test@example.com")[0];
7. Provide fallback if filter()
returns an empty array
const items = list.filter(i => i.available);
if (items.length === 0) {
console.log("No available items found.");
}
🌍 Real-world Scenario
In a React app, use filter()
to show only available products and find()
to get one user’s details by ID for editing or viewing.
Object.keys() & Object.values()
🧠 Detailed Explanation
Imagine you have an object in JavaScript like this:
const user = {
name: "Ali",
age: 25,
role: "developer"
};
🔹 Object.keys()
gives you an array of the property names (the keys):
["name", "age", "role"]
🔹 Object.values()
gives you an array of the values:
["Ali", 25, "developer"]
These methods are super useful when you want to loop over an object — like showing a user’s details on a page or turning form fields into editable inputs.
👉 Both methods are built into JavaScript and work with any plain object (not arrays or classes).
Bonus Tip: You can combine them with loops like forEach
or map()
to work with each key or value.
💡 Examples
Example 1: Display all keys of an object
const car = {
brand: "Toyota",
model: "Corolla",
year: 2022
};
const keys = Object.keys(car);
console.log(keys);
// Output: ["brand", "model", "year"]
Explanation: This helps when you want to know the structure or labels of your data.
Example 2: Display all values of an object
const car = {
brand: "Toyota",
model: "Corolla",
year: 2022
};
const values = Object.values(car);
console.log(values);
// Output: ["Toyota", "Corolla", 2022]
Explanation: You can use this to get user-facing content from a backend response object.
Example 3: Loop over object keys and display both key and value
const profile = {
name: "Aisha",
country: "Pakistan",
status: "Active"
};
Object.keys(profile).forEach((key) => {
console.log(`${key}: ${profile[key]}`);
});
// Output:
// name: Aisha
// country: Pakistan
// status: Active
Explanation: Perfect for rendering a dynamic profile page from an API object.
Example 4: Count how many properties an object has
const product = {
title: "Laptop",
price: 1200,
stock: 30
};
const numberOfFields = Object.keys(product).length;
console.log(numberOfFields);
// Output: 3
Explanation: You can use this to check how complete an object is or for analytics.
Example 5: Convert object values into a dropdown
const colors = {
primary: "Blue",
secondary: "Green",
danger: "Red"
};
Object.values(colors).map(color => {
return <option key={color} value={color}>{color}</option>;
});
Explanation: This is common when you want to populate a select box with dynamic values in React or Rails frontend using React + ERB.
🔁 Alternatives
For both keys and values together, use Object.entries()
which returns an array of [key, value] pairs:
Object.entries(user);
// => [["name", "Aisha"], ["age", 28], ["role", "admin"]]
❓ General Questions & Answers
Q1: What does Object.keys()
return?
A: It returns an array of the object’s own property names (keys). These are the labels or identifiers of the data stored in the object.
const user = { name: "Ali", age: 30 };
Object.keys(user); // ["name", "age"]
Q2: What does Object.values()
return?
A: It returns an array of the object’s values — the actual data stored inside the object.
Object.values(user); // ["Ali", 30]
Q3: Can I use these methods on arrays?
A: Yes, but be careful — the keys are the index numbers as strings, and values are the elements.
Object.keys(["a", "b", "c"]); // ["0", "1", "2"]
Object.values(["a", "b", "c"]); // ["a", "b", "c"]
Q4: What happens if the object is empty?
A: Both methods return an empty array.
Object.keys({}); // []
Object.values({}); // []
Q5: Do these methods include nested object keys or inherited properties?
A: No. They only return own properties of the object, and do not go inside nested objects or inherited ones.
const person = { name: "Sara", details: { age: 30 } };
Object.keys(person); // ["name", "details"]
To go deeper, you’d need recursion or Object.entries()
combined with iteration.
🛠️ Technical Q&A with Solutions
Q1: How can I loop through both key and value of an object?
A: Use Object.entries()
which gives you key-value pairs in an array. Then use forEach
or for...of
to loop through them.
const product = { name: "Bag", price: 100 };
Object.entries(product).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// Output:
// name: Bag
// price: 100
Q2: How do I count how many properties an object has?
A: Use Object.keys().length
to get the number of top-level keys.
const user = { name: "Ali", role: "admin" };
console.log(Object.keys(user).length); // 2
Q3: How can I filter keys that start with a specific letter?
A: Use Object.keys()
and filter()
together:
const data = { name: "Zain", nickname: "Z", age: 28 };
const filtered = Object.keys(data).filter(key => key.startsWith("n"));
console.log(filtered); // ["name", "nickname"]
Q4: Can I get all values from nested objects?
A: Not directly. Object.values()
only works for the top level. You need recursion for deep extraction.
const user = {
name: "Ali",
details: {
age: 30,
country: "Pakistan"
}
};
console.log(Object.values(user));
// ["Ali", { age: 30, country: "Pakistan" }]
Q5: Can I use these methods on class instances?
A: Yes, but only if the properties are not inherited through the prototype chain.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const p = new Person("Hassan", 35);
console.log(Object.keys(p)); // ["name", "age"]
console.log(Object.values(p)); // ["Hassan", 35"]
✅ Best Practices with Examples
1. Use Object.entries()
when you need both keys and values
✅ It’s cleaner and avoids looking up the value manually using the key.
const user = { name: "Aisha", age: 25 };
Object.entries(user).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
2. Check if it’s an object before using Object.keys()
or Object.values()
⚠️ Avoid errors by ensuring the value isn’t null or undefined.
function printKeys(obj) {
if (obj && typeof obj === "object") {
console.log(Object.keys(obj));
} else {
console.log("Not a valid object");
}
}
3. Use Object.values()
to easily build UI elements
✅ Useful for building select/dropdown menus dynamically.
const colors = { primary: "Blue", danger: "Red", success: "Green" };
Object.values(colors).map((color) => {
return <option key={color} value={color}>{color}</option>;
});
4. Use Object.keys().length
to check if an object has data
✅ This is better than checking if Object.keys()
is truthy.
if (Object.keys(data).length === 0) {
console.log("Object is empty");
}
5. Prefer array methods for arrays
⚠️ Object.keys()
will return string indexes for arrays — not ideal.
const arr = ["a", "b", "c"];
Object.keys(arr); // ["0", "1", "2"] — not very helpful
✅ Use arr.map()
instead.
🌍 Real-World Examples
1. Rendering dynamic form fields from a config object
const formConfig = {
username: "text",
email: "email",
age: "number"
};
Object.keys(formConfig).map((field) => (
<input
type={formConfig[field]}
name={field}
placeholder={`Enter ${field}`}
key={field}
/>
));
2. Displaying user profile info dynamically
const user = { name: "Sara", role: "Admin", active: true };
Object.entries(user).map(([key, value]) => (
<p>{key}: {value.toString()}</p>
));
3. Filtering object keys for settings page
const settings = {
theme: "dark",
language: "en",
betaAccess: false
};
const visibleKeys = Object.keys(settings).filter(key => key !== "betaAccess");
4. Checking if API response object is empty
if (Object.keys(apiResponse).length === 0) {
console.log("No data received");
}
5. Rendering product specs in an e-commerce app
const specs = {
RAM: "8GB",
Storage: "256GB SSD",
Processor: "Intel i5"
};
Object.entries(specs).map(([spec, detail]) => (
<li><strong>{spec}:</strong> {detail}</li>
));
6. Mapping values to a dropdown menu
const roles = {
admin: "Admin",
user: "User",
guest: "Guest"
};
Object.values(roles).map(role => (
<option value={role}>{role}</option>
));
7. Counting the number of properties in a JSON config
const config = {
featureA: true,
featureB: false,
featureC: true
};
console.log("Total settings:", Object.keys(config).length);
Short-circuiting (&&)
🧠 Detailed Explanation
In JavaScript, the &&
(AND) operator can be used not just for conditions, but also for a trick called short-circuiting.
Here’s the idea: condition && doSomething()
will only run doSomething()
if the condition
is true.
If the left side of &&
is truthy, JavaScript will continue and return the right side.
If the left side is falsy (like false
, 0
, null
, undefined
, or ""
), JavaScript will stop there and return that falsy value.
true && "Hello"
→ returns"Hello"
false && "Hello"
→ returnsfalse
🔥 This is very helpful in React to conditionally show or hide elements:
{isLoggedIn && <Dashboard />}
will only render the Dashboard if isLoggedIn
is true.
🧠 Think of &&
like a guard: if the first part is true, the second part is allowed to run. If it’s false, nothing happens.
💡 Examples
Example 1: Basic Short-Circuiting
// This will log only if isAdmin is true
const isAdmin = true;
isAdmin && console.log("Welcome, Admin!");
// Output: Welcome, Admin!
Explanation: The message will only show if isAdmin
is true. If it’s false, nothing happens.
Example 2: React – Conditional Rendering
function Dashboard({ user }) {
return (
<div>
{user && <p>Welcome, {user.name}</p>}
</div>
);
}
Explanation: If user
is not null
or undefined
, the greeting is shown. Otherwise, nothing renders.
Example 3: Skipping execution when falsy
const email = "";
email && console.log("Send welcome email");
// Output: nothing happens (email is empty string)
Explanation: Because email
is falsy (empty string), the console.log()
is skipped.
Example 4: Returning values based on a condition
const user = { name: "Ali", premium: true };
const badge = user.premium && "🌟 Premium Member";
console.log(badge); // Output: "🌟 Premium Member"
Explanation: This is a nice shortcut for setting values based on a flag.
Example 5: Nested Short-circuiting
const settings = { notifications: true, sound: false };
settings.notifications && settings.sound && console.log("🔔 Play sound");
// Output: nothing happens because sound is false
Explanation: Even if notifications are on, the second check fails so the action is skipped.
🔁 Alternative Concepts
You can use the ternary operator for the same purpose if you also want to render an “else” block:
{isLoggedIn ? <Dashboard /> : <Login />}
❓ General Questions & Answers
Q1: What does “short-circuiting” mean in JavaScript?
A: Short-circuiting means stopping the evaluation of an expression as soon as the result is known. For the &&
operator, if the first value is falsy, the second part is skipped completely.
false && console.log("This won't run");
// Output: nothing
Why? Because false
is falsy, so JavaScript doesn’t bother checking the second part.
Q2: Can I use && for conditional rendering in React?
A: Yes! It’s a popular pattern for rendering UI only when a condition is true.
{isLoggedIn && <Dashboard />}
This will render the <Dashboard />
component only if isLoggedIn
is true.
Q3: What’s the difference between &&
and if
statements?
A: They both can be used for conditions, but &&
is used inline. It’s shorter, great for one-liners, especially in JSX.
// Using if
if (isOnline) {
console.log("User is online");
}
// Using &&
isOnline && console.log("User is online");
Q4: What happens if both sides of && are true?
A: The right-hand value is returned.
true && "Hello"; // returns "Hello"
Q5: What if I use a non-boolean value on the left?
A: JavaScript will evaluate it based on its truthy/falsy nature. The left side doesn’t have to be a true boolean.
const name = "Ali";
name && console.log("Hello", name); // Output: Hello Ali
If name
were an empty string, nothing would be logged.
🛠️ Technical Q&A with Examples
Q1: Can I use short-circuiting inside a function return?
A: Yes, especially useful in inline return logic like in JSX.
function Greeting({ user }) {
return (
<>
<h1>Home Page</h1>
{user && <p>Welcome, {user.name}!</p>}
</>
);
}
Explanation: If user
exists, the welcome message is shown.
Q2: What’s the return value of a && b
?
A: If a
is truthy, it returns b
. If a
is falsy, it returns a
.
true && "Hi" // → "Hi"
false && "Hi" // → false
"hello" && 123 // → 123
"" && 123 // → ""
Q3: Is &&
better than if
in React?
A: It’s not “better,” but it’s concise for conditional rendering.
{messages.length > 0 && <p>You have messages</p>}
Note: It only works well when you want to show something or nothing. If you need an “else,” use ternary
instead.
Q4: Can short-circuiting be used to avoid errors with undefined values?
A: Yes, it can help prevent runtime errors.
const user = null;
console.log(user && user.name); // → null (no error)
Without &&
, trying user.name
would throw an error if user
is null.
Q5: Can I chain multiple &&
conditions?
A: Yes, but make sure all earlier expressions are necessary.
isLoggedIn && hasPermission && <DeleteButton />
This renders <DeleteButton />
only if both are true.
✅ Best Practices with Examples
1. Use &&
for clean, simple conditional rendering
Ideal for showing elements only when a condition is true.
{isLoggedIn && <UserProfile />}
✅ Clean and readable — avoids unnecessary if
blocks.
2. Avoid writing complex logic inside the &&
expression
✅ Move logic to a function for clarity.
// ❌ Too complex
{user.isActive && user.isVerified && user.role === "admin" && <AdminPanel />}
// ✅ Better
function canViewAdmin(user) {
return user.isActive && user.isVerified && user.role === "admin";
}
{canViewAdmin(user) && <AdminPanel />}
3. Avoid using &&
with values that could be valid but falsy
// ❌ This will skip rendering if count is 0
{count && <p>You have {count} messages</p>}
// ✅ Use explicit check
{count !== 0 && <p>You have {count} messages</p>}
💡 0
is falsy, but still valid for display — so avoid accidental skipping.
4. Prioritize readability
✅ Don’t sacrifice clarity just to save a few lines.
// ✅ Clear
{isVisible && <Tooltip />}
// ❌ Less readable
isVisible && show && type === "info" && <Tooltip />
5. Combine with ??
when you expect null
or undefined
const message = user?.name && `Hello, ${user.name}`;
console.log(message ?? "Guest");
🌍 Real-World Examples of Short-Circuiting (&&)
1. React: Conditional Component Render
{user && <Welcome name={user.name} />}
👉 Only show the welcome message if the user
exists.
2. Prevent Form Submission If Empty
email && handleSubmit(email);
👉 Only run handleSubmit
if email
has a value.
3. Showing Notification Count
{notifications.length > 0 && <span>🔔 {notifications.length}</span>}
👉 Display notification icon only if there are items.
4. Execute Function Only If Condition Passes
isAdmin && showAdminPanel();
👉 Run a function only if the user is an admin.
5. Display Text Conditionally
const name = "Ali";
const greeting = name && `Hello, ${name}`;
// greeting = "Hello, Ali"
6. Safe Access in JSX
<p>{user && user.name}</p>
👉 Avoids “cannot read property of undefined” errors.
7. Guarding JSX Blocks
{isLoggedIn && (
<div className="menu">
<a href="/profile">My Profile</a>
<a href="/logout">Logout</a>
</div>
)}
👉 Entire menu block is skipped if not logged in.
⏱️ setTimeout() & setInterval()
🧠 Detailed Explanation
In JavaScript, setTimeout()
and setInterval()
are used to control time-based actions.
-
setTimeout()
: Runs a function once after a delay.
➤ Think of it as “Do this after some time”. -
setInterval()
: Runs a function again and again at a fixed time gap.
➤ Think of it as “Do this every few seconds”.
These are very useful when you want to delay something (like showing a message), or repeat something (like updating a clock or auto-saving a form).
How they work:
setTimeout(() => { ... }, 2000)
→ Runs after 2 secondssetInterval(() => { ... }, 1000)
→ Runs every 1 second until stopped
To stop setInterval()
, use clearInterval()
. To cancel setTimeout()
, use clearTimeout()
.
In React: You can combine these with useEffect
to control how and when things run.
💡 Examples
Example 1: Using setTimeout()
to show a message after 2 seconds
// JavaScript
setTimeout(() => {
alert("This message shows after 2 seconds!");
}, 2000);
Explanation: The alert box appears 2 seconds after the script runs.
Example 2: Using setInterval()
to show the time every second
setInterval(() => {
const now = new Date();
console.log("Time now:", now.toLocaleTimeString());
}, 1000);
Explanation: This prints the current time to the console every second.
Example 3: Stop an interval after a few repetitions
let count = 0;
const timer = setInterval(() => {
console.log("Running...", count);
count++;
if (count === 5) {
clearInterval(timer);
console.log("Stopped after 5 runs.");
}
}, 1000);
Explanation: This code runs 5 times and then stops itself using clearInterval()
.
Example 4: Use setTimeout()
to hide a banner after 3 seconds
const banner = document.getElementById("promo");
setTimeout(() => {
banner.style.display = "none";
}, 3000);
Explanation: This hides the HTML element with id “promo” after 3 seconds.
Example 5: setInterval()
in React (with cleanup)
import { useEffect } from "react";
function TimerComponent() {
useEffect(() => {
const intervalId = setInterval(() => {
console.log("Tick...");
}, 1000);
return () => clearInterval(intervalId); // Cleanup
}, []);
return <p>Check console for ticks!</p>;
}
Explanation: This runs inside a React component and cleans up when unmounted to avoid memory leaks.
🔁 Alternatives
Modern alternatives include:
- requestAnimationFrame() – for smoother animations.
- useEffect + setTimeout/setInterval in React.
❓ General Questions & Answers
Q1: What’s the difference between setTimeout
and setInterval
?
A:
setTimeout()
runs a function once after a delay.
setInterval()
runs the same function repeatedly every few milliseconds until you stop it.
Example:
setTimeout(() => alert("Hi!"), 2000)
→ shows alert once after 2 seconds.
setInterval(() => console.log("Hi"), 1000)
→ logs “Hi” every 1 second.
Q2: How do I cancel or stop a timer?
A:
Use clearTimeout()
to cancel a timeout, and clearInterval()
to stop an interval.
First, store the timer ID:
const id = setTimeout(...)
Then cancel it with: clearTimeout(id)
or clearInterval(id)
.
Q3: Can I use setTimeout
and setInterval
in React?
A: Yes, but it’s important to clean them up. Use them inside useEffect()
and return a cleanup function:
useEffect(() => { const timer = setTimeout(...); return () => clearTimeout(timer); }, []);
Q4: What happens if I forget to clear an interval?
A: The function will keep running forever, even if the component is no longer shown (in React). This can cause memory leaks and unexpected behavior.
Q5: What’s a common real-life use case for each?
A:
setTimeout
→ show a toast message that disappears after 3 seconds.
setInterval
→ update a live digital clock every second.
🛠️ Technical Q&A with Examples
Q1: How do I stop setInterval()
after 5 times?
A: Use a counter variable and clearInterval()
when the count reaches 5.
let count = 0;
const intervalId = setInterval(() => {
console.log("Run #", count + 1);
count++;
if (count === 5) {
clearInterval(intervalId);
console.log("Stopped!");
}
}, 1000);
Q2: How do I use setTimeout()
inside a React component?
A: Wrap it in useEffect()
and clear it on unmount.
import { useEffect } from "react";
function DelayedMessage() {
useEffect(() => {
const timer = setTimeout(() => {
alert("Welcome!");
}, 2000);
return () => clearTimeout(timer); // cleanup
}, []);
return <p>Message will show after 2 seconds</p>;
}
Q3: What if I want to use setInterval()
in React but clear it when the component unmounts?
A: Do the same as setTimeout
— just use clearInterval()
instead.
useEffect(() => {
const id = setInterval(() => {
console.log("Repeating every second");
}, 1000);
return () => clearInterval(id); // cleanup on unmount
}, []);
Q4: Can I dynamically change the delay time in setTimeout()
?
A: Yes. Use a variable or state value as the delay:
const delay = 3000;
setTimeout(() => {
console.log("Waited 3 seconds");
}, delay);
Q5: Is there a difference between passing an arrow function vs named function to setTimeout
?
A: Not functionally. But for readability and reuse, named functions are better:
// Arrow
setTimeout(() => console.log("Done"), 1000);
// Named
function sayDone() {
console.log("Done");
}
setTimeout(sayDone, 1000);
✅ Best Practices with Examples
1. Always clear intervals and timeouts
✅ Prevents memory leaks and unwanted executions.
// ❌ Don't forget to clear intervals
const id = setInterval(() => console.log("Tick"), 1000);
// ✅ Best practice in React or vanilla JS
clearInterval(id);
2. Use named functions instead of inline for reusability
✅ Improves readability and helps in debugging.
function showAlert() {
alert("Hello!");
}
setTimeout(showAlert, 2000); // ✅ Good
3. Use useEffect
in React for cleanup
✅ Ensures timers are removed when component unmounts.
useEffect(() => {
const id = setTimeout(() => {
console.log("Done");
}, 3000);
return () => clearTimeout(id); // ✅ Cleanup
}, []);
4. Avoid setting state inside interval without cleanup
⚠️ It causes memory leaks or double execution in React.
useEffect(() => {
const id = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(id); // ✅ Required
}, []);
5. Prefer setTimeout
recursion over setInterval
if delays may vary
✅ Gives more control over timing and execution.
function repeatTask() {
doSomething();
setTimeout(repeatTask, 2000); // custom interval control
}
repeatTask();
🌍 Real-World Examples
- 1. Auto-hiding notifications
setTimeout(() => hideToast(), 3000)
– Used to auto-dismiss toast messages after 3 seconds. - 2. Countdown Timer
setInterval()
is perfect for creating countdowns by updating the UI every second. - 3. Carousel/Slider transitions
Automatically switch slides every few seconds usingsetInterval()
. - 4. Debounced UI feedback
UsesetTimeout()
to delay user input processing in search or filter bars. - 5. Loading spinner delay
Show a loading spinner for at least 2 seconds usingsetTimeout()
even if data comes instantly. - 6. Typing animation
Reveal characters one-by-one to simulate a typing effect using recursivesetTimeout()
. - 7. Auto-logout session timer
Track inactivity and automatically log out the user usingsetTimeout()
and reset on action.
JavaScript Promise
🧠 Detailed Explanation
A Promise in JavaScript is like a “promise” you make to the computer:
“I’ll get the data soon — and when I do, I’ll let you know what happened.”
Promises are used to handle things that take time to complete — like:
- Fetching data from a server
- Waiting for a file to load
- Delaying an action (like a timeout)
A promise has 3 states:
Pending
– The work is still going on.Fulfilled
– The work is done successfully.Rejected
– Something went wrong.
You can react to the result using:
.then()
– runs if the promise is successful.catch()
– runs if there is an error
Why it’s useful: Promises help write clean, readable code without deep nesting (known as “callback hell”).
Remember: A promise is not about getting the result *now*, but getting it *later*, once the task finishes.
💡 Examples
✅ Example 1: Basic Promise
// Step 1: Create a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("✅ Task completed successfully!");
} else {
reject("❌ Task failed.");
}
});
// Step 2: Use .then() to handle success and .catch() for errors
myPromise
.then(message => {
console.log("Then:", message);
})
.catch(error => {
console.log("Catch:", error);
});
Explanation: This example creates a simple promise. If `success` is true, it runs .then()
; otherwise it runs .catch()
.
✅ Example 2: Simulating a network request with a timeout
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("📦 Data fetched from server!");
}, 2000); // 2 seconds delay
});
}
fetchData().then(data => {
console.log(data);
});
Explanation: This promise simulates a delay like you might see when calling an API.
✅ Example 3: Chaining Promises
function firstStep() {
return new Promise(resolve => {
resolve("Step 1 complete");
});
}
function secondStep(message) {
return new Promise(resolve => {
resolve(message + " → Step 2 complete");
});
}
firstStep()
.then(result => secondStep(result))
.then(final => console.log(final));
// Output: Step 1 complete → Step 2 complete
Explanation: You can chain multiple .then()
calls to pass results through steps.
✅ Example 4: Using Promise.all()
to wait for multiple promises
const promise1 = Promise.resolve("🚀 Load header");
const promise2 = Promise.resolve("📦 Load content");
const promise3 = Promise.resolve("🎨 Load footer");
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log("All loaded:", values);
});
Explanation: This runs all promises at the same time and waits for all of them to finish.
✅ Example 5: Catching Errors
new Promise((resolve, reject) => {
reject("Something went wrong");
})
.then(result => console.log(result))
.catch(error => console.error("Caught:", error));
Explanation: This shows how to handle errors gracefully using .catch()
.
🔁 Alternative Methods or Concepts
- Callback Functions – the older way of handling async logic
- async/await – modern, cleaner syntax built on top of Promises
- Promise.all, Promise.race – for handling multiple promises
❓ General Questions & Answers
Q1: What is a Promise in JavaScript?
A: A Promise is a way to handle something that will complete in the future (like a network request). Instead of blocking the code while waiting, the promise allows the rest of the code to run and then provides the result when it’s ready.
Q2: What are the states of a Promise?
A: A promise has 3 possible states:
Pending
– The operation hasn’t finished yet.Fulfilled
– The operation completed successfully.Rejected
– The operation failed.
Q3: What’s the difference between .then()
and .catch()
?
A:
.then()
runs if the promise is successful..catch()
runs if there is an error or rejection.
Q4: Why are Promises useful?
A: Promises help keep asynchronous code clean and organized. Instead of writing deeply nested callbacks (callback hell), you can chain .then()
and .catch()
blocks in a readable way.
Q5: Can we use Promises with async/await?
A: Yes! Async/await is built on top of Promises. It lets you write asynchronous code that looks like synchronous code, making it easier to read and write.
🛠️ Technical Q&A with Examples
Q1: How do I create a custom Promise that resolves after 2 seconds?
A: Use setTimeout()
inside a new Promise.
function delayedGreeting() {
return new Promise(resolve => {
setTimeout(() => {
resolve("👋 Hello after 2 seconds!");
}, 2000);
});
}
delayedGreeting().then(message => console.log(message));
✅ Tip: This is great for testing loading states or simulating API delay.
Q2: How do I handle multiple asynchronous tasks in parallel?
A: Use Promise.all()
to wait for all tasks to complete.
const task1 = Promise.resolve("🧱 Task 1 done");
const task2 = Promise.resolve("🔧 Task 2 done");
const task3 = Promise.resolve("⚙️ Task 3 done");
Promise.all([task1, task2, task3])
.then(results => {
console.log("✅ All done:", results);
});
✅ Tip: Great for loading multiple parts of a UI at once.
Q3: How do I catch errors inside a Promise chain?
A: Use .catch()
at the end of the chain to handle any error.
new Promise((resolve, reject) => {
reject("❌ Something broke");
})
.then(result => console.log(result))
.catch(error => console.error("Caught:", error));
✅ Tip: Always add .catch()
to handle unexpected failures.
Q4: How do I turn a function into a Promise-returning function?
A: Wrap the logic in a new Promise
and resolve/reject as needed.
function checkAge(age) {
return new Promise((resolve, reject) => {
if (age >= 18) {
resolve("✔️ You are eligible!");
} else {
reject("❌ You must be 18 or older.");
}
});
}
checkAge(21)
.then(msg => console.log(msg))
.catch(err => console.error(err));
Q5: How do I use async/await
with Promises?
A: Use async
to declare the function and await
for the Promise result.
async function fetchMessage() {
const message = await Promise.resolve("✅ Hello from a promise!");
console.log(message);
}
fetchMessage();
✅ Tip: This approach is cleaner and easier to read than chaining .then()
.
✅ Best Practices with Examples
1. Always handle errors using .catch()
or try/catch
✅ Prevent your app from crashing by catching failed promises.
// ✅ Good
fetch('/api/data')
.then(res => res.json())
.catch(error => console.error('Fetch failed:', error));
// Or with async/await
async function loadData() {
try {
const res = await fetch('/api/data');
const data = await res.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
2. Use async/await
for cleaner and more readable code
✅ It’s easier to follow than chaining .then()
especially for multiple steps.
// ✅ Good
async function getUser() {
const res = await fetch('/api/user');
const user = await res.json();
return user;
}
3. Avoid nesting Promises unless necessary
✅ Chain them or use async/await
instead of nesting to reduce complexity.
// ❌ Bad
login().then(user => {
getProfile(user.id).then(profile => {
console.log(profile);
});
});
// ✅ Good
login()
.then(user => getProfile(user.id))
.then(profile => console.log(profile));
4. Use Promise.all()
when tasks are independent and can run in parallel
✅ It’s faster and cleaner.
const loadA = fetch('/api/a');
const loadB = fetch('/api/b');
Promise.all([loadA, loadB])
.then(([resA, resB]) => {
// Use both results together
});
5. Don’t mix async/await
and .then()
without reason
✅ Pick one style for consistency and readability.
// ❌ Unnecessary mix
async function fetchData() {
const res = await fetch('/api/items');
res.json().then(data => console.log(data)); // avoid this
}
// ✅ Better
async function fetchData() {
const res = await fetch('/api/items');
const data = await res.json();
console.log(data);
}
🌍 Real-world Examples
- 1. Fetching User Data from an API
fetch('/api/user').then(res => res.json()).then(user => console.log(user));
Used in dashboards, profiles, etc. - 2. Submitting a Form Asynchronously
fetch('/submit', { method: 'POST', body: formData })
Used to post contact forms, feedback, or orders without page reload. - 3. Waiting for Multiple API Responses
Promise.all([fetch('/a'), fetch('/b')])
Used when loading related data like user and user posts together. - 4. Retry on Failure
Wrap a failed fetch in a Promise retry logic with exponential backoff.
Useful in unstable network conditions (e.g., mobile apps). - 5. Image or File Upload
uploadImage().then(() => alert('Uploaded!'))
Common in admin panels, CMSs, or portfolio sites. - 6. Payment Confirmation
processPayment().then(showReceipt)
Used in e-commerce platforms or subscriptions. - 7. Show Loading Spinners
setLoading(true); fetch('/data').then(() => setLoading(false));
Used in React/Vue apps to improve UX.
Async/Await (Promises)
🧠 Detailed Explanation
In JavaScript, some tasks take time — like getting data from an API. These tasks don’t finish right away, so we use something called a Promise to wait for the result.
async
and await
are tools that help you write this kind of code more easily.
async
function: A function that always returns a Promise.await
: Pauses the function until the Promise is done (either success or error).- ✅ It makes code look like regular code — easier to read and understand.
For example, instead of writing this:
// Using .then()
fetch('/user')
.then(response => response.json())
.then(data => console.log(data));
You can write this instead:
async function getUser() {
const res = await fetch('/user');
const data = await res.json();
console.log(data);
}
Much simpler, right? That’s the power of async/await
— it helps you write clean and easy-to-read asynchronous code.
💡 Examples
Example 1: Basic Data Fetching
async function getUser() {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const user = await response.json();
console.log(user);
}
getUser();
Explanation: This code waits for the API to respond, then converts it into JSON, and logs the user.
Example 2: Handling Errors with try/catch
async function fetchData() {
try {
const res = await fetch('/some-api');
const data = await res.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}
Explanation: If something goes wrong (like a network error), the catch block handles it gracefully.
Example 3: Async inside a button click
function App() {
async function handleClick() {
const res = await fetch('/user');
const user = await res.json();
alert(`Hello, ${user.name}`);
}
return <button onClick={handleClick}>Load User</button>;
}
Explanation: This waits for the user data to load after the button is clicked, then shows an alert.
Example 4: Multiple Await Calls in a Row
async function loadInfo() {
const userRes = await fetch('/user');
const user = await userRes.json();
const postsRes = await fetch(`/posts?user=${user.id}`);
const posts = await postsRes.json();
console.log('User:', user);
console.log('Posts:', posts);
}
Explanation: First it gets the user, then it uses the user’s ID to get their posts. One after another.
Example 5: Run Multiple Promises at Once
async function loadAll() {
const [user, posts] = await Promise.all([
fetch('/user').then(res => res.json()),
fetch('/posts').then(res => res.json())
]);
console.log(user, posts);
}
Explanation: This runs both requests in parallel and waits for both to finish before continuing.
🔁 Alternatives
.then()
and.catch()
chaining (Promise style)- Callbacks (older way, harder to manage)
❓ General Questions & Answers
Q1: What is the purpose of async
and await
in JavaScript?
A:
async
makes a function always return a Promise, and await
is used to pause execution until a Promise is resolved. Together, they make asynchronous code look and behave like synchronous code — easier to read and debug.
Q2: Why is await
only allowed inside async functions?
A: Because await
needs to pause an asynchronous function. If used outside an async
function, JavaScript wouldn’t know where to wait. To use await
, wrap your code in an async
function.
Q3: What happens if an error occurs in an async function?
A: If an error occurs in an async function, it’s treated like a rejected Promise. You can handle it using try...catch
blocks to avoid crashes.
Q4: Can I use await
with non-Promise values?
A: Yes! If you await
a regular value (like a number or string), it simply returns that value. It’s harmless but not needed — best used with actual Promises.
Q5: What is the difference between .then()
and await
?
A: Both wait for Promises, but:
.then()
uses chaining and callbacks — good for short logic.await
lets you write code step-by-step, like regular synchronous code — cleaner for complex flows.
🛠️ Technical Q&A with Examples
Q1: How do I handle errors inside an async function?
A: Use try...catch
blocks to catch and manage any errors during the async process.
async function loadUser() {
try {
const res = await fetch('/user');
const data = await res.json();
console.log(data);
} catch (err) {
console.error('Something went wrong:', err);
}
}
Q2: How can I run multiple async calls at once?
A: Use Promise.all()
to execute async operations in parallel and wait for all of them.
async function loadData() {
const [users, posts] = await Promise.all([
fetch('/users').then(res => res.json()),
fetch('/posts').then(res => res.json())
]);
console.log(users, posts);
}
Q3: What if I want to delay something inside an async function?
A: Use setTimeout
with a Promise wrapper to create a delay.
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function delayedAlert() {
await delay(1000);
alert('1 second passed!');
}
Q4: Can I return values from an async function?
A: Yes. An async function always returns a Promise that resolves to the returned value.
async function getName() {
return 'John';
}
getName().then(name => console.log(name)); // John
Q5: Can I use await inside loops?
A: Yes, but be cautious. It will wait for each loop iteration, making it slower than Promise-based batch processing.
async function loadItems(ids) {
for (const id of ids) {
const res = await fetch(`/items/${id}`);
const item = await res.json();
console.log(item);
}
}
✅ Tip: Use this when you need sequential execution. For parallel execution, use Promise.all()
.
✅ Best Practices with Examples
1. Always use try/catch
to handle errors
✅ Prevents your app from crashing due to unhandled Promise rejections.
async function loadUser() {
try {
const res = await fetch('/user');
const data = await res.json();
console.log(data);
} catch (err) {
console.error('Error:', err);
}
}
2. Use async/await instead of chaining .then()
✅ Makes the code cleaner and easier to follow.
// ✅ Preferred
async function getData() {
const res = await fetch('/api');
const data = await res.json();
return data;
}
// ❌ Less readable
fetch('/api')
.then(res => res.json())
.then(data => console.log(data));
3. Use Promise.all()
for parallel requests
✅ Speeds up execution when multiple tasks can run independently.
async function loadData() {
const [users, posts] = await Promise.all([
fetch('/users').then(res => res.json()),
fetch('/posts').then(res => res.json())
]);
}
4. Wrap setTimeout
in a Promise for delays
✅ Allows you to use await
with time-based delays.
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function pause() {
await sleep(1000);
console.log("1 second later");
}
5. Avoid top-level await
outside modules
✅ Only use await
inside async functions or ES modules.
// ❌ Will throw an error in non-module environments
const res = await fetch('/api');
// ✅ Correct way
async function load() {
const res = await fetch('/api');
}
🌍 Real-world Examples
- 1. Fetching User Profile on Login
After login, useasync/await
to fetch the user’s profile and preferences from the server before rendering the dashboard. - 2. Submitting a Form with Server Validation
On form submit, await the server response to validate fields and show errors without refreshing the page. - 3. Loading Multiple API Endpoints
UsePromise.all()
withasync/await
to load user data, notifications, and settings in parallel for a dashboard. - 4. Delaying a Popup or Toast Notification
After a user action, delay showing a success message using anawait sleep()
function. - 5. Uploading Files in Sequence
Await each file upload one by one for ordered uploads with a progress bar. - 6. Polling Server for Status Updates
Use a loop +await fetch()
+await sleep()
to poll a long-running task like video processing. - 7. Handling Login with Redirect
Await login API response, store the token, and redirect to the appropriate page afterward usingwindow.location
.
Closures
🧠 Detailed Explanation
A closure is when a function can access variables from its outer (or parent) function even after the parent function has finished running.
Think of it like this: if a function is created inside another function, it can “remember” the variables defined in the outer function—even if it’s used later.
This lets you create functions with “private” variables and helps keep your code clean and safe from unwanted changes.
Closures are one of the most powerful and important features in JavaScript, especially useful when dealing with things like:
- ✅ Keeping data private (like counters or settings)
- ✅ Avoiding global variables
- ✅ Writing modular, reusable code
🧠 Tip: If a function uses a variable, and that variable isn’t inside it, JavaScript looks “outside” for it — this is where closures come into play!
💡 Examples
Example 1: Basic Closure
function outerFunction() {
let message = "Hello from outer!";
function innerFunction() {
console.log(message);
}
return innerFunction;
}
const greet = outerFunction();
greet(); // Output: "Hello from outer!"
Explanation: Even though outerFunction
has finished executing, innerFunction
still remembers the variable message
. This is closure in action.
Example 2: Counter with Private State
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
Explanation: count
is private to the function returned by createCounter
. It stays in memory due to closure, allowing the function to “remember” previous values.
Example 3: Closure in a Loop (with let vs var)
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log("With let:", i);
}, 1000);
}
// Using var would not work the same way
Explanation: Because let
has block scope, each loop iteration gets its own copy of i
. The closure created inside setTimeout
captures the correct value.
Example 4: Closures with Parameters
function multiplier(factor) {
return function(num) {
return num * factor;
};
}
const double = multiplier(2);
console.log(double(5)); // 10
Explanation: The inner function remembers the factor
value from when multiplier
was called. So double
always multiplies by 2.
Example 5: Simulating Private Variables
function Person(name) {
let secret = "I love JavaScript";
return {
getName: function() {
return name;
},
getSecret: function() {
return secret;
}
};
}
const dev = Person("Aisha");
console.log(dev.getName()); // Aisha
console.log(dev.getSecret()); // I love JavaScript
Explanation: secret
is not accessible directly, but the inner functions can access it via closure. This pattern is often used for encapsulation.
🔁 Alternatives or Similar Concepts
- Using classes to manage private data
- Modules or IIFEs for encapsulation
❓ General Questions & Answers
Q1: What is a closure in JavaScript?
A: A closure is when a function “remembers” the variables from its surrounding scope, even after that outer function has finished executing.
Example:
function outer() {
let x = 10;
return function inner() {
console.log(x);
};
}
const fn = outer();
fn(); // still logs 10 even after outer() is done
Q2: Why are closures useful?
A: Closures let us:
- Access variables from an outer function scope after the outer function has returned
- Create private variables
- Maintain state in functions like counters or event handlers
Q3: What’s a real-life example of a closure?
A: Think of a **backpack**. When you leave your house (outer function), you take the backpack (closure) with you, and it still has access to things (variables) from the house.
In code:
function backpack() {
let snacks = "Cookies";
return function() {
return snacks;
};
}
const grabSnack = backpack();
console.log(grabSnack()); // "Cookies"
Q4: Can closures access updated variables?
A: Yes! Closures don’t freeze values — they reference them. So if the outer variable changes, the closure sees the latest value.
Example:
function demo() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = demo();
counter(); // 1
counter(); // 2
Q5: Are closures only in JavaScript?
A: No. Closures exist in many languages like Python, Ruby, Swift, etc. But in JavaScript, they are especially important because of how asynchronous and functional JavaScript is.
🛠️ Technical Q&A with Examples
Q1: How can closures be used to create private variables?
A: By returning a function that still references a local variable, you prevent direct access to that variable from outside.
// Counter with a private variable
function createCounter() {
let count = 0; // private
return {
increment: () => ++count,
getCount: () => count
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined (not accessible)
Q2: How does closure memory work inside loops?
A: In loops, closures can cause unexpected behavior if not handled properly.
// Problem
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// Output: 3 3 3 (after 1 sec)
// Fix with IIFE (Immediately Invoked Function Expression)
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 1000);
})(i);
}
// Output: 0 1 2
Q3: Can closures be used to cache expensive operations?
A: Yes! You can memoize values inside a closure to avoid recalculating them.
function memoizeAdd() {
const cache = {};
return function(num) {
if (cache[num]) {
console.log("Fetching from cache");
return cache[num];
}
console.log("Calculating result");
const result = num + 20;
cache[num] = result;
return result;
};
}
const add = memoizeAdd();
console.log(add(5)); // Calculating result → 25
console.log(add(5)); // Fetching from cache → 25
Q4: How does a closure behave in async functions?
A: Closures still hold their references even after asynchronous delays.
function delayedLogger() {
let message = "Saved by closure!";
setTimeout(function() {
console.log(message); // Still logs the message
}, 1000);
}
delayedLogger();
Q5: Can you create multiple independent closures from the same function?
A: Yes! Each invocation creates a new closure with its own scope.
function makeMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
✅ Best Practices with Examples
1. Use closures to encapsulate logic and protect internal variables
Closures help you create private state that isn't exposed outside the function. Great for counters, caching, or configuration patterns.
function createLogger() {
let logs = [];
return {
log: (msg) => logs.push(msg),
getLogs: () => [...logs]
};
}
const logger = createLogger();
logger.log("User logged in");
console.log(logger.getLogs()); // ["User logged in"]
2. Avoid memory leaks in long-lived closures
If closures reference large structures (like DOM elements), remove or nullify those references when done.
// Avoid holding onto unused DOM elements
function setupTooltip() {
let tooltip = document.getElementById("tip");
return function show() {
tooltip.style.display = "block";
};
}
const showTip = setupTooltip();
// If tooltip is removed from DOM, ensure cleanup
// tooltip = null;
3. Don’t overuse closures when a regular function or class is more appropriate
Closures are powerful, but using too many nested functions can hurt readability.
// ❌ Overcomplicated
function outer() {
let x = 10;
return function() {
return function() {
console.log(x);
};
};
}
// ✅ Cleaner
function showX() {
const x = 10;
return () => console.log(x);
}
4. Use closures to cache results and improve performance
This is also called “memoization” — useful for slow calculations or repeated data fetching.
function memoizeSquare() {
const cache = {};
return function(n) {
if (cache[n]) return cache[n];
const result = n * n;
cache[n] = result;
return result;
};
}
const square = memoizeSquare();
console.log(square(4)); // 16 (calculated)
console.log(square(4)); // 16 (from cache)
5. Always name your inner functions if reused
This improves debugging and stack traces.
function setupCounter() {
let count = 0;
function increment() {
count++;
return count;
}
return increment;
}
6. Be cautious using closures inside loops — use let or IIFE
// ✅ Using let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// ✅ Or IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 1000);
})(i);
}
🌍 7 Real-World Examples of Closures
-
1. Counter Function – Each call to the factory function gives a private counter:
function createCounter() { let count = 0; return function() { return ++count; }; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2
-
2. Hiding Data in a Module – Encapsulate variables in a function scope:
const UserModule = (() => { let username = "secretUser"; return { getName: () => username, setName: (name) => (username = name), }; })(); console.log(UserModule.getName()); // secretUser
-
3. Event Handler with Data – Maintain data context in an event:
function setupClickLogger(message) { return function () { console.log("Clicked: " + message); }; } document.getElementById("btn").onclick = setupClickLogger("Save Button");
-
4. Delayed Execution (setTimeout) – Access variables later:
function greetLater(name) { setTimeout(function () { console.log("Hello, " + name); }, 1000); } greetLater("Aisha");
-
5. Memoization (Caching Function Results) – Store expensive results:
function memoize() { const cache = {}; return function(n) { if (cache[n]) return cache[n]; return cache[n] = n * n; }; } const square = memoize(); console.log(square(4)); // 16 console.log(square(4)); // Cached: 16
-
6. Toggle Functionality – Keep track of state across calls:
function createToggle() { let isVisible = false; return function () { isVisible = !isVisible; return isVisible; }; } const toggleMenu = createToggle(); console.log(toggleMenu()); // true console.log(toggleMenu()); // false
-
7. Custom Hook Pattern in React – React’s useState uses closures internally:
function useCustomCounter() { let count = 0; return { increment: () => ++count, getValue: () => count, }; } const counter = useCustomCounter(); counter.increment(); console.log(counter.getValue()); // 1
Memoization (useMemo, useCallback)
🧠 Detailed Explanation
In React, when your component re-renders, all the code inside it runs again — even the functions and calculations.
Sometimes, we don’t want to re-run a function or calculation every time. That’s where memoization helps!
- useMemo: It remembers (or "memoizes") the result of a function.
- useCallback: It remembers the function itself so it's not created again and again.
✅ They both help improve performance by stopping unnecessary re-calculations or re-creations.
Think of it like this: instead of baking a cake every time someone visits, you keep one ready in the fridge — that’s memoization!
💡 Examples
Example 1: useMemo for Expensive Calculation
import { useMemo } from "react";
function ExpensiveComponent({ number }) {
const double = useMemo(() => {
console.log("Calculating...");
return number * 2;
}, [number]);
return <p>Double: {double}</p>;
}
Explanation: The multiplication only re-runs when number
changes. It skips calculation on other renders.
Example 2: useCallback for Button Click Handler
import { useCallback } from "react";
function MyComponent({ name }) {
const sayHello = useCallback(() => {
alert("Hello, " + name);
}, [name]);
return <button onClick={sayHello}>Greet</button>;
}
Explanation: The sayHello
function is not recreated unless name
changes. This is great for performance and when passing to child components.
Example 3: Why It Matters in a List Component
function Parent() {
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
return <Child onClick={handleClick} />;
}
Explanation: If handleClick
was recreated on every render, the Child
might re-render too. useCallback avoids this.
Example 4: useMemo for Filtering Large List
const filteredItems = useMemo(() => {
return items.filter(item => item.startsWith("A"));
}, [items]);
Explanation: The filtering only happens again if the items
list changes. Saves CPU power!
🔁 Alternatives
- Using memoized selectors like
reselect
in Redux. - Pure functions with
React.memo()
on components.
❓ General Questions & Answers
Q1: What is memoization in React?
A: Memoization is a way to save the result of a function so that it's not recalculated on every render. React provides useMemo
and useCallback
to help with this. They're helpful when your component does heavy work (like loops or calculations) or passes functions to child components.
Q2: What's the difference between useMemo
and useCallback
?
A:
- useMemo
returns a **value** (result of a function).
- useCallback
returns a **function** itself.
Example:
const value = useMemo(() => calculate(), []);
const callback = useCallback(() => doSomething(), []);
Use useMemo
when you want to store values, and useCallback
when you're passing functions to child components.
Q3: When should I use useMemo
?
A: Use useMemo
when you have a calculation or filter that takes time or might cause unnecessary re-renders. It makes sure the calculation only runs when needed.
Q4: Why does my child component re-render even when props haven’t changed?
A: Probably because you're passing a new function as a prop on every render. Even if it does the same thing, React sees it as “new.” Wrap that function in useCallback
to prevent this.
Q5: Is it okay to always use useMemo
and useCallback
?
A: Not always. Use them only when there’s a clear performance benefit. Overusing them can make your code harder to read and might even slow things down slightly due to the extra memory tracking.
🛠️ Technical Q&A with Examples
Q1: How does useMemo
improve performance in a component?
A: It stores the result of an expensive calculation and reuses it unless its dependencies change.
// Without memoization
const filteredItems = items.filter(item => item.active);
// With useMemo
const filteredItems = useMemo(() => {
return items.filter(item => item.active);
}, [items]);
✅ This ensures the filter only runs again if items
change.
Q2: What problem does useCallback
solve when passing functions to child components?
A: React sees new functions as different every render. useCallback
returns the same function unless dependencies change, preventing unnecessary child renders.
// Parent Component
const handleClick = useCallback(() => {
console.log("Clicked!");
}, []);
<Child onClick={handleClick} />
✅ This helps avoid re-renders in memoized child components.
Q3: Why is my useMemo
not working as expected?
A: Common issues include:
- Incorrect or missing dependency array.
- Returning undefined instead of a value.
// Incorrect: Missing dependency
useMemo(() => compute(), []); // compute uses props.value but not listed in deps
✅ Always list all dependencies used inside the function.
Q4: Can useMemo
or useCallback
be used inside loops or conditions?
A: ❌ No. React hooks must be called at the top level of your component. Do not wrap them in if
, for
, or while
.
Q5: What’s the difference between React.memo
and useCallback
?
A:
- React.memo
prevents re-rendering of a component unless props change.
- useCallback
ensures a stable function reference is passed to React.memo
components.
// Parent
const handleClick = useCallback(() => doSomething(), []);
<MemoizedButton onClick={handleClick} />
// Child
const MemoizedButton = React.memo(({ onClick }) => {
return <button onClick={onClick}>Click</button>;
});
✅ Best Practices with Examples
1. Use useMemo
for expensive calculations
Only memoize when the computation is costly and repeated unnecessarily.
const expensiveValue = useMemo(() => {
return computeHeavy(items);
}, [items]);
2. Avoid overusing useMemo
Don’t memoize simple expressions — it can add complexity without benefits.
// ❌ Unnecessary
const doubled = useMemo(() => value * 2, [value]);
// ✅ Simple calc, just use:
const doubled = value * 2;
3. Use useCallback
for stable function references in child props
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
<ChildButton onClick={increment} />
✅ Keeps props stable so React.memo
children don’t re-render unnecessarily.
4. Combine with React.memo
for full optimization
const Child = React.memo(({ onClick }) => {
return <button onClick={onClick}>Add</button>;
});
5. Always set dependency arrays correctly
If any external variable is used inside the callback or memoized value, include it in the dependency array.
useCallback(() => doSomething(props.id), [props.id])
6. Profile before optimizing
Don’t add useMemo
or useCallback
just in case. Use React DevTools Profiler to confirm performance gains.
7. Clean up with useEffect
when using memoized callbacks
If a memoized callback sets timers or event listeners, always clean them up.
const handler = useCallback(() => {
// do something
}, []);
useEffect(() => {
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, [handler]);
🌍 Real-World Examples
1. Filtering a large list of products
Use useMemo
to filter only when the search term or data changes.
const filteredProducts = useMemo(() => {
return products.filter(product => product.name.includes(search));
}, [products, search]);
2. Memoizing formatted dates for chat messages
Don’t reformat every time if the input doesn’t change.
const formattedDate = useMemo(() => formatDate(timestamp), [timestamp]);
3. Avoiding re-renders in charts (D3.js or Chart.js)
Wrap your data in useMemo
so chart doesn’t redraw on each render.
const chartData = useMemo(() => generateChart(data), [data]);
4. Stable function for pagination buttons
Use useCallback
to pass to child pagination controls.
const goToPage = useCallback((page) => setPage(page), []);
5. Complex calculations in a financial dashboard
Like interest rates, forecasts, or currency conversions.
const forecast = useMemo(() => {
return calculateForecast(currentAmount, interestRate);
}, [currentAmount, interestRate]);
6. Optimizing form components with many fields
Pass stable handlers to avoid re-renders.
const handleInputChange = useCallback((e) => {
setForm({ ...form, [e.target.name]: e.target.value });
}, [form]);
7. Preventing recalculation of derived state
If you're deriving one state from another, use useMemo
to avoid performance issues.
const completedTasks = useMemo(() => {
return tasks.filter(task => task.completed);
}, [tasks]);
Shallow vs Deep Copy
🧠 Detailed Explanation
When we copy an object or array in JavaScript, there are two ways the copy can behave:
- Shallow Copy: Only copies the top-level parts of the object. If the object has other objects inside it (like nested objects or arrays), those still point to the original. So, changing them in the copy will affect the original.
- Deep Copy: Makes a complete copy, including all nested parts. The new object is fully separate — changes in the copy won’t affect the original.
🔹 Think of a shallow copy like copying the outer box but leaving the stuff inside shared.
🔹 A deep copy gives you a new box *and* all new contents inside it.
This matters when you want to avoid unexpected changes in your data — especially in state management, forms, or when working with nested data in React or Redux.
💡 Examples
Example 1: Shallow Copy using Object.assign()
const original = {
name: "John",
address: {
city: "Lahore",
zip: 54000
}
};
const shallowCopy = Object.assign({}, original);
shallowCopy.address.city = "Karachi";
console.log(original.address.city); // ❗ Output: "Karachi"
Explanation: Even though we copied the object, the address
object inside is still shared. Changing it in the copy also changes the original.
Example 2: Deep Copy using JSON.parse(JSON.stringify())
const original = {
name: "John",
address: {
city: "Lahore",
zip: 54000
}
};
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.address.city = "Karachi";
console.log(original.address.city); // ✅ Output: "Lahore"
Explanation: With deep copy, we clone everything — even the nested address
. Changes in the deep copy won’t affect the original.
Example 3: Shallow Copy with Array Spread
const originalArray = [{ item: "Book" }, { item: "Pen" }];
const shallowCopy = [...originalArray];
shallowCopy[0].item = "Notebook";
console.log(originalArray[0].item); // ❗ Output: "Notebook"
Explanation: The spread operator only copies top-level elements. If those elements are objects, they still point to the same memory.
Example 4: Deep Copy using structuredClone (Modern JS)
const original = {
user: {
name: "Alice",
skills: ["React", "Node"]
}
};
const deepClone = structuredClone(original);
deepClone.user.name = "Bob";
console.log(original.user.name); // ✅ Output: "Alice"
Explanation: structuredClone()
is a newer and cleaner way to do deep cloning in modern JavaScript.
🔁 Alternative Methods
- ✅
structuredClone()
(modern and native deep copy) - ✅
lodash.cloneDeep()
from Lodash library - ⚠️
JSON.parse(JSON.stringify(...))
— simple but fails on methods, dates, undefined, circular refs
❓ General Questions & Answers
Q1: What is a shallow copy?
A: A shallow copy copies the top-level properties of an object or array. But if any of those properties are objects (or arrays), it just copies the reference — not the actual object. So, changes to nested objects in the copied version will also affect the original.
Q2: What is a deep copy?
A: A deep copy creates a brand-new copy of everything, including nested objects or arrays. So, changes made to the deep copy do not affect the original at all. It’s a true, independent copy.
Q3: When should I use a deep copy?
A: You should use a deep copy when you need to work with a completely separate version of an object — especially when it has nested objects or arrays. This prevents unexpected changes to the original data.
Q4: What are some ways to make a deep copy in JavaScript?
A: You can use:
JSON.parse(JSON.stringify(object))
– Easy and works for most datastructuredClone(object)
– Best for modern browsers- Libraries like Lodash’s
_.cloneDeep()
– Great for complex data
Q5: Can I use the spread operator (...) for deep copying?
A: No, the spread operator only creates a shallow copy. If you have nested objects, they will still point to the same memory, which can lead to bugs if you modify them.
🛠️ Technical Q&A with Examples
Q1: How does the spread operator create a shallow copy?
A: When using {...object}
or [...array]
, only the first level of properties is copied. Nested objects or arrays are still shared by reference.
const user = {
name: "Alice",
address: { city: "New York" }
};
const copy = { ...user };
copy.address.city = "Los Angeles";
console.log(user.address.city); // "Los Angeles"
✅ Explanation: Even though we made a copy, address
still points to the same object in memory.
Q2: How to create a deep copy using JSON methods?
A: You can use JSON.stringify()
and JSON.parse()
to serialize and then deserialize an object, effectively cloning it.
const user = {
name: "Bob",
address: { city: "Berlin" }
};
const deepCopy = JSON.parse(JSON.stringify(user));
deepCopy.address.city = "London";
console.log(user.address.city); // "Berlin"
✅ Tip: This only works if your data doesn’t contain functions, dates, or undefined values (they get lost in the conversion).
Q3: What’s a modern way to deep copy an object?
A: In modern JavaScript environments, you can use structuredClone()
.
const original = { name: "Luna", details: { age: 3 } };
const copy = structuredClone(original);
copy.details.age = 5;
console.log(original.details.age); // 3
✅ Explanation: This is a safe and modern browser API to deep clone almost anything.
Q4: How do libraries like Lodash perform deep cloning?
A: Lodash offers a utility function _.cloneDeep()
which handles many edge cases like circular references.
import _ from 'lodash';
const obj = { x: 1, nested: { y: 2 } };
const clone = _.cloneDeep(obj);
clone.nested.y = 10;
console.log(obj.nested.y); // 2
✅ Tip: Lodash is a robust choice for production-level deep cloning.
Q5: Can I mix shallow and deep copying?
A: Yes, sometimes developers use shallow copy for outer structure and deep copy manually for specific nested parts.
const user = {
name: "Elena",
settings: {
theme: "dark",
preferences: { notifications: true }
}
};
const userCopy = {
...user,
settings: {
...user.settings,
preferences: { ...user.settings.preferences }
}
};
✅ Tip: This gives you fine control while avoiding unnecessary full deep copies.
✅ Best Practices with Examples
1. Use shallow copies for simple objects
✅ If your object or array doesn’t contain nested structures, a shallow copy using spread
or Object.assign()
is fine.
// Shallow copy example
const original = { name: "Alex", age: 30 };
const copy = { ...original };
2. Avoid shallow copy for nested objects or arrays
⚠️ Shallow copying shared references can lead to bugs because nested data still points to the same memory.
const user = { name: "Eva", address: { city: "Rome" } };
const shallowCopy = { ...user };
shallowCopy.address.city = "Paris";
console.log(user.address.city); // ❌ "Paris" (unexpected!)
3. Use structuredClone or libraries for safe deep cloning
✅ Use structuredClone()
(modern browsers) or _.cloneDeep()
(Lodash) for deeply nested structures.
const deep = structuredClone(complexObject);
// OR using Lodash
import _ from 'lodash';
const deep = _.cloneDeep(complexObject);
4. Clone only what you need
✅ Don’t deep copy the entire object unless necessary. Target only the nested parts you need.
const copy = {
...original,
nested: {
...original.nested,
sub: { ...original.nested.sub }
}
};
5. Avoid JSON.parse(JSON.stringify()) when object has special types
⚠️ This method won’t copy functions
, undefined
, Date
, or Map/Set
.
const withDate = { time: new Date() };
const copied = JSON.parse(JSON.stringify(withDate));
console.log(copied.time); // ❌ Not a Date anymore
6. Prefer immutability in state management
✅ Always return new objects when modifying state — never mutate the original directly (especially in React).
setState(prev => ({
...prev,
user: {
...prev.user,
name: "Updated"
}
}));
7. Always test cloning behavior
✅ Add tests for object/array clones in sensitive areas like forms, preferences, and dynamic data.
🌍 Real-World Use Cases
- 1. React State Update: In React, to avoid mutating state directly, you often shallow copy the object using the spread operator to update part of the state safely.
- 2. Undo Feature in Editors: Text or drawing editors like Figma or Notion use deep copy to save snapshots of complex nested objects for undo/redo functionality.
- 3. Redux Store Updates: Redux relies heavily on immutable updates. Developers use shallow copies to ensure state is updated correctly without mutating the original state.
- 4. Cloning API Response: When receiving nested JSON from an API and modifying it (e.g., for form population), deep cloning ensures the original response isn’t changed.
- 5. Duplicating Configurations: When copying user preferences or dashboard settings (e.g., charts, widgets), deep copy ensures independence between old and new configurations.
- 6. Form Draft Recovery: In form builders or CMS systems, a deep clone helps save and restore complete drafts that may include nested sections, inputs, or media attachments.
- 7. Game Development: In games (e.g., chess, tic-tac-toe), cloning the current board state (a nested array or object) is essential for features like move history and AI simulations.
Event Bubbling & stopPropagation()
🧠 Detailed Explanation
Imagine you have a big box (like a card) and inside it, there's a button. When you click the button, not only does the button’s click function run — the box’s click function runs too. This is called event bubbling.
It’s like dropping a pebble in a pond — the effect spreads outward. The event starts from the place you clicked (the button) and travels up through all the parent elements (like the box).
Sometimes you don’t want this to happen — like when clicking inside a modal should not close it. In those cases, you can use event.stopPropagation()
to stop the event from “bubbling up.”
So in short:
- Event Bubbling = Click event goes from the child to all parent elements.
- stopPropagation() = Stops the click from reaching the parent.
💡 Examples
Example 1: Event Bubbling
If you click the button, both the button and the parent container will trigger a message.
<div onClick={() => alert('Container clicked!')}>
<button onClick={() => alert('Button clicked!')}>Click Me</button>
</div>
What happens? First, you’ll see "Button clicked!", then "Container clicked!" — because the event bubbles up to the parent.
Example 2: Stopping the Event from Bubbling
Now, we stop the event from reaching the container by using event.stopPropagation()
inside the button's click handler.
<div onClick={() => alert('Container clicked!')}>
<button onClick={(e) => {
e.stopPropagation();
alert('Button clicked!');
}}>Click Me</button>
</div>
What happens? Only "Button clicked!" appears. The container never gets the event.
Example 3: Why This Is Useful (Closing a Modal)
When you click the background, the modal closes. But clicking inside the modal content should NOT close it. We use stopPropagation()
to prevent that.
// Modal container
<div className="modal-background" onClick={closeModal}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<p>Modal Content</p>
</div>
</div>
What happens? Clicking outside (on the background) closes the modal. Clicking inside (on content) doesn’t trigger the close function.
🔁 Alternative Methods or Concepts
- stopImmediatePropagation() – Prevents further listeners of the same event from being called.
- Event Delegation – Use bubbling to your advantage by adding listeners to parent elements.
❓ General Questions & Answers
Q1: What is event bubbling?
A: Event bubbling is a concept where an event starts from the innermost element (the target) and then moves up to its ancestors (parents, grandparents, etc.). For example, if you click a button inside a <div>
, both the button and the <div>
can react to the click, unless you stop it.
Q2: What is event.stopPropagation()
?
A: It's a JavaScript method used to stop the event from bubbling up to parent elements. So, if you don’t want your click on a button to also trigger a click on its container, you call event.stopPropagation()
in the button’s handler.
Q3: Why would you want to stop bubbling?
A: To avoid unintended behavior. For example, if clicking outside a modal closes it, but clicking inside the modal also triggers that same close action (because of bubbling), you'll need to stop the event from reaching the outer handler.
Q4: Is event bubbling the same as event capturing?
A: No. Event bubbling goes from the innermost element to the outer elements (child to parent). Event capturing is the opposite — it starts from the outermost element down to the target (parent to child). Bubbling is the default behavior in browsers.
Q5: Do all events bubble?
A: Not all. Most common ones like click
, submit
, keydown
, etc. do. But some like focus
and blur
do not bubble by default.
🛠️ Technical Q&A with Examples
Q1: How do I prevent a click inside a modal from triggering an outer click handler?
A: Use event.stopPropagation()
inside the inner element’s handler to stop the event from bubbling to the parent.
function Modal() {
const closeModal = () => alert("Modal closed");
return (
<div onClick={closeModal}>
<div onClick={(e) => e.stopPropagation()}>
<p>Modal content here</p>
</div>
</div>
);
}
✅ Result: Clicking inside the modal doesn't close it; only clicks outside will trigger closeModal()
.
Q2: Can I listen for the same event in both child and parent?
A: Yes, both will receive the event unless you use stopPropagation()
.
function Parent() {
const handleParentClick = () => alert("Parent clicked");
const handleChildClick = () => alert("Child clicked");
return (
<div onClick={handleParentClick}>
<button onClick={handleChildClick}>Click Me</button>
</div>
);
}
Note: Clicking the button triggers both alerts unless you stop the event in the child.
Q3: How can I check the event propagation path?
A: Use event.target
and event.currentTarget
to track where the event originated and where it’s currently being handled.
function Logger() {
const logEvent = (e) => {
console.log("Target:", e.target);
console.log("Current Target:", e.currentTarget);
};
return (
<div onClick={logEvent}>
<button>Click Me</button>
</div>
);
}
Output: Shows the actual clicked element vs. the one with the listener.
Q4: Can I stop both bubbling and the default behavior?
A: Yes. Use both event.stopPropagation()
and event.preventDefault()
.
function Form() {
const handleSubmit = (e) => {
e.preventDefault();
e.stopPropagation();
alert("Form intercepted!");
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
Q5: How do I capture events before they bubble?
A: You can use the capture
phase by passing capture
as true
in native JS, or onClickCapture
in React.
<div onClickCapture={() => console.log("Capture phase")} onClick={() => console.log("Bubble phase")}>
<button>Click Me</button>
</div>
✅ Output: "Capture phase" logs before "Bubble phase".
✅ Best Practices with Examples
1. Use stopPropagation()
only when necessary
🔸 Preventing event bubbling can break parent component functionality. Only use it when you're intentionally blocking parent event handlers (e.g., modals).
// Good
function Modal() {
return (
<div onClick={closeModal}>
<div onClick={(e) => e.stopPropagation()}>
This won't close the modal
</div>
</div>
);
}
2. Keep event handling logic outside JSX
✅ Improves readability and testability of event logic.
// Good
function handleClick() {
alert("Clicked!");
}
<button onClick={handleClick}>Click</button>
3. Know the difference between event.target
and event.currentTarget
🔍 target
is the clicked element. currentTarget
is the element the handler is on.
function ClickLogger(e) {
console.log("Clicked:", e.target);
console.log("Handled by:", e.currentTarget);
}
4. Avoid unnecessary nesting of clickable elements
⚠️ Reduces bugs related to accidental propagation.
5. Use onClickCapture
to intercept events before bubbling
✅ Good for logging, analytics, or stopping events early.
<div onClickCapture={() => console.log("Captured first")}>
<button>Click Me</button>
</div>
6. Don’t block propagation in shared components
❌ Blocking bubbling in components like buttons or dropdowns can break other parent features like modals or analytics tracking.
7. Use comments when stopping propagation
💬 Future developers should understand why you're stopping the event chain.
onClick={(e) => {
// Stop click from closing modal
e.stopPropagation();
}}
🌍 7 Real-World Use Cases
-
1. Closing a modal when clicking outside:
Use a parent click listener to close the modal, andstopPropagation()
inside the modal to prevent accidental closure. -
2. Dropdown menu toggle:
When clicking on dropdown items, prevent the click from closing the menu by stopping propagation. -
3. Accordion UI component:
Clicking on inner content shouldn’t collapse the section. UsestopPropagation()
in the content’s handler. -
4. Analytics tracking:
You may want to log only clicks on certain sections (e.g., a button inside a card), not the card itself — differentiate usingevent.target
. -
5. Preventing double actions in nested forms:
If a form is nested inside another container with its own submit/click listener, usestopPropagation()
to prevent triggering both. -
6. Notification banners:
If the entire notification card is clickable, but also has a "Dismiss" button, usestopPropagation()
to prevent triggering the card’s default behavior. -
7. Confirmation dialogs in complex UIs:
Prevent outer clicks when confirming actions inside a component (like confirm/cancel buttons in a popover).
Rest Parameters (...args)
🧠 Detailed Explanation
Rest parameters in JavaScript allow a function to accept an unknown number of arguments and store them as an array. It uses the special syntax ...args
, where args
is just a name you can choose.
You write it like this:
function showAll(...items) {
console.log(items);
}
showAll("apple", "banana", "cherry");
✅ Output: ["apple", "banana", "cherry"]
The rest parameter must be the last one in your function’s parameter list. It gathers all remaining arguments into a real array, so you can use array methods like .map()
, .reduce()
, or .filter()
.
🔹 Think of it as a way to collect “the rest” of the arguments.
📌 Key Use Case: When you want a flexible function that can take any number of inputs (e.g., summing numbers, logging messages, combining values).
💡 Examples
Example 1: Summing Numbers
function sumAll(...numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sumAll(10, 20, 30)); // ➜ 60
Explanation: ...numbers
collects all the arguments into an array, and reduce()
adds them up.
Example 2: Logging All Messages
function logMessages(...messages) {
messages.forEach(msg => console.log(msg));
}
logMessages("Start", "Processing", "Complete");
Explanation: It prints each argument separately using a loop. Great for debug or notifications.
Example 3: Combine with Regular Parameters
function greet(greeting, ...names) {
names.forEach(name => console.log(`${greeting}, ${name}!`));
}
greet("Hello", "Alice", "Bob", "Charlie");
Explanation: The first argument goes to greeting
, and the rest go into names
array.
Example 4: Filtering Rest Arguments
function onlyNumbers(...items) {
return items.filter(item => typeof item === "number");
}
console.log(onlyNumbers(1, "hello", true, 42, null)); // ➜ [1, 42]
Explanation: This filters out only numeric values from a mixed group of arguments.
Example 5: Count Arguments Passed
function countArgs(...args) {
return `You passed ${args.length} arguments.`;
}
console.log(countArgs("a", "b", "c")); // ➜ You passed 3 arguments.
Explanation: Shows how many arguments the function received dynamically.
🔁 Alternative Concepts
Arguments object: In regular functions (not arrow functions), arguments
is similar to rest parameters but less flexible and not a real array.
❓ General Questions & Answers
Q1: What are rest parameters?
A:
Rest parameters allow you to pass an unlimited number of arguments into a function.
Instead of defining each one separately, you use ...name
to gather them all into a single array.
Example:
function show(...args) {
console.log(args);
}
show(1, 2, 3); // ➜ [1, 2, 3]
Q2: What’s the difference between rest parameters and arguments object?
A: Both let you access all arguments passed to a function, but:
- Rest parameters use actual arrays and allow array methods.
arguments
is array-like but not a real array (no map, filter, etc.).
Q3: Can I use rest parameters with other parameters?
A: Yes, but rest must always come last. Example:
function greet(greeting, ...names) {
// 'greeting' is first, rest are in 'names'
}
Q4: Can I use rest parameters in arrow functions?
A: Absolutely! Just like in regular functions:
const printArgs = (...args) => console.log(args);
printArgs(1, 2, 3);
Q5: What happens if I don’t pass any arguments to a rest parameter?
A: The rest parameter will be an empty array:
function test(...data) {
console.log(data); // ➜ []
}
test(); // No arguments
🛠️ Technical Q&A with Examples
Q1: How do I sum all numbers passed to a function using rest parameters?
A: You can loop through the rest parameter array or use reduce()
.
function sumAll(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sumAll(5, 10, 15)); // ➜ 30
Q2: How do rest parameters help create flexible functions?
A: They allow functions to accept any number of arguments without changing the function definition.
function joinWords(separator, ...words) {
return words.join(separator);
}
console.log(joinWords("-", "HTML", "CSS", "JS")); // ➜ "HTML-CSS-JS"
Q3: What if I try to put rest parameters before other arguments?
A: You'll get a syntax error. Rest parameters must always be the last in the function signature.
// ❌ Invalid
function wrong(...rest, last) {} // SyntaxError
// ✅ Correct
function correct(first, ...rest) {}
Q4: How do rest parameters behave with destructuring?
A: You can combine them with array destructuring to capture remaining values.
const [first, ...others] = [10, 20, 30, 40];
console.log(first); // ➜ 10
console.log(others); // ➜ [20, 30, 40]
Q5: Can I use rest parameters in a class method?
A: Yes! Works just like in regular functions.
class Logger {
log(...messages) {
messages.forEach(msg => console.log("Log:", msg));
}
}
const logger = new Logger();
logger.log("Start", "Running", "Done");
✅ Best Practices with Examples
1. Use rest parameters only when truly needed
✅ Use them when the number of arguments is unknown or flexible.
function printAll(...items) {
items.forEach(item => console.log(item));
}
printAll("React", "Vue", "Angular");
2. Always put rest parameters at the end
✅ It's required syntactically and ensures predictable behavior.
// Correct
function combine(base, ...others) {
return [base, ...others];
}
3. Use descriptive names instead of just ...args
✅ Improves readability and intention of the code.
function addUsers(...usernames) {
usernames.forEach(name => console.log(`User: ${name}`));
}
4. Avoid mixing too many argument types
⚠️ Don’t rely on argument order or type-checking in rest arrays unless necessary.
function logEvents(...events) {
events.forEach(e => {
if (typeof e === "string") {
console.log("Event:", e);
}
});
}
5. Combine with destructuring for advanced use
✅ Useful when dealing with arrays or parameters that need separation.
function processScores(first, ...rest) {
console.log("Top score:", first);
console.log("Other scores:", rest);
}
6. Prefer ...args
over the arguments
object
✅ Rest parameters are cleaner, modern, and work in arrow functions.
// Old way ❌
function oldWay() {
console.log(arguments);
}
// New way ✅
function newWay(...args) {
console.log(args);
}
🌍 7 Real-World Examples
- 1. Logging multiple arguments: A logging utility that accepts any number of messages and prints them with timestamps.
- 2. Building dynamic SQL queries: A function receives table name and multiple filter conditions, then constructs a dynamic query string.
- 3. Creating custom validators: A form validation function accepts multiple validation rules and applies them in sequence.
- 4. Combining user permissions: A role-assignment function receives one primary role and several optional roles to merge.
- 5. Aggregating financial entries: A transaction logger that adds all numbers passed and saves a summary.
- 6. React event tracking: A handler that logs event names passed through props like: `
`. - 7. Custom command line tool: A CLI parser receives all commands typed and executes matching logic using rest parameters.
== vs === (Type Coercion & Equality)
🧠 Detailed Explanation
In JavaScript, ==
and ===
are both used to compare values — but they behave differently:
==
(Loose Equality): Compares values after converting them to the same type.===
(Strict Equality): Compares both the value and the type without changing them.
This means that ==
might give unexpected results because JavaScript tries to guess and convert types. On the other hand, ===
is more predictable because it checks everything exactly.
Here’s a simple comparison:
5 == '5' // true → type is different, but values are equal after coercion
5 === '5' // false → value is same, but types are different (number vs string)
So, using ===
helps avoid bugs caused by automatic type conversion.
✅ Summary: Use ===
most of the time unless you know why you need ==
.
💡 Examples
Example 1: Comparing a number and a string
5 == '5' // true
5 === '5' // false
Why? ==
converts '5' (string) to 5 (number), but ===
sees different types.
Example 2: Comparing boolean and number
0 == false // true
0 === false // false
Why? ==
converts false to 0. ===
checks type — one is a number, the other is a boolean.
Example 3: Null and undefined
null == undefined // true
null === undefined // false
Why? ==
considers them equal loosely, ===
sees different types.
Example 4: Same value and type
42 == 42 // true
42 === 42 // true
Why? Both value and type are the same. So both comparisons return true.
Example 5: Comparing objects
{} == {} // false
{} === {} // false
Why? Objects are compared by reference, not value — even if they look the same.
🔁 Alternatives
You can use strict equality ===
by default and only use ==
if you're intentionally comparing across types with coercion.
❓ General Questions & Answers
Q1: Why is 0 == false
true but 0 === false
false?
A: ==
coerces the operands into the same type, so it converts false
to 0. But ===
compares value AND type, and 0
is a number while false
is a boolean.
Q2: When should I use ===
?
A: Always, unless you have a specific reason to use ==
. It avoids bugs caused by type coercion.
🛠️ Technical Q&A with Example
Q: What happens when you compare [] == false
?
console.log([] == false); // true
A: The array is coerced to an empty string ''
, then to 0. false
also coerces to 0, so they are equal.
✅ Best Practices
- Always use
===
unless type coercion is absolutely needed. - Linting tools like ESLint can warn against
==
usage. - Write predictable, type-safe comparisons to avoid logical bugs.
🌍 Real-world Scenario
While comparing user input with database values, always use ===
to ensure accurate match without accidental type coercion.
IIFE (Immediately Invoked Function Expression)
🧠 Detailed Explanation
An IIFE stands for Immediately Invoked Function Expression. It's a function in JavaScript that runs as soon as it's defined.
Normally, you define a function and then call it separately:
function greet() {
console.log("Hello");
}
greet();
With an IIFE, you define and call the function at the same time:
(function() {
console.log("Hello from IIFE!");
})();
This pattern is useful when you want to:
- ✅ Execute code immediately without needing to call it later
- 🔒 Create a private scope to avoid polluting global variables
- 🧼 Run setup code like configuration or bootstrapping
Think of IIFEs like self-starting engines — once you write them, they immediately run and do their job without being called again.
💡 Examples
Example 1: Simple IIFE
(function() {
console.log("This runs immediately!");
})();
Explanation: This function defines itself and immediately runs. You’ll see the message printed as soon as the script loads.
Example 2: IIFE with Parameters
(function(name) {
console.log("Hello, " + name);
})("Aisha");
Explanation: The function takes a parameter and is immediately invoked with "Aisha" as the argument.
Example 3: IIFE for Variable Privacy
var result = (function() {
var secret = "hidden value";
return secret;
})();
console.log(result); // "hidden value"
Explanation: The variable secret
is not accessible outside the IIFE, but we can return it if needed. This keeps things private.
Example 4: IIFE to Avoid Global Variables
(function() {
var counter = 0;
counter++;
console.log(counter); // 1
})();
// console.log(counter); ❌ Error: counter is not defined
Explanation: The variable counter
lives only inside the IIFE and doesn’t leak to the global scope.
🔁 Alternative Concepts
While ES6 modules or closures can now achieve the same result, IIFEs are still useful in quick scripts or older codebases.
❓ General Questions & Answers
Q1: What is an IIFE?
A: IIFE stands for Immediately Invoked Function Expression. It is a function in JavaScript that runs as soon as it is defined. It's useful for creating private scopes and avoiding polluting the global namespace.
Q2: Why use an IIFE?
A: You use IIFEs to create a new scope that protects variables from being accessible globally. This is especially useful when writing modular or encapsulated code in JavaScript before modules were common.
Q3: What does the syntax look like?
A: It looks like this:
(function() {
console.log("Runs immediately");
})();
The parentheses around the function turn it into an expression. The second set of parentheses ()
calls the function immediately.
Q4: Can IIFE accept parameters?
A: Yes. You can pass parameters to an IIFE just like any regular function:
(function(name) {
console.log("Hi " + name);
})("Ali");
Q5: Are IIFEs still used today?
A: While IIFEs are less common in modern JavaScript (thanks to ES6 modules and let
/const
), they are still useful in some situations like older codebases, or when you want to create a scope immediately inside a script tag or inline block.
🛠️ Technical Q&A with Examples
Q1: How does JavaScript know a function is an IIFE?
A: JavaScript interprets a function as an IIFE when it is wrapped in parentheses (function() {})()
. The wrapping converts the function declaration into an expression, and the trailing ()
invokes it immediately.
// IIFE Syntax
(function() {
console.log("I run immediately");
})();
Q2: Can IIFE return a value?
A: Yes. An IIFE can return any value, just like a normal function.
const result = (function() {
return 42;
})();
console.log(result); // Output: 42
Q3: How does IIFE help in avoiding global scope pollution?
A: IIFEs create a private scope where variables exist only inside the function, not globally.
(function() {
const message = "Private!";
console.log(message);
})();
console.log(message); // ❌ ReferenceError
Q4: Can I use arrow functions with IIFE?
A: Yes! Arrow functions work with IIFE too.
(() => {
console.log("Arrow IIFE runs!");
})();
Q5: How to pass arguments to an IIFE?
A: Arguments are passed in the second set of parentheses:
(function(name) {
console.log("Welcome, " + name);
})("Aisha");
✅ Best Practices with Examples
1. Use IIFE for Initialization Code
✅ Wrap setup or configuration logic that only needs to run once when the script loads.
(function initializeApp() {
console.log("App Initialized");
// perform initial setup
})();
2. Avoid Global Scope Pollution
✅ Keep variables local to prevent clashes with other scripts.
(function() {
const temp = "temporary value";
console.log(temp);
})();
console.log(temp); // ❌ ReferenceError
3. Group Related Code Inside IIFE
✅ Makes it easier to manage and modularize functionality.
(function() {
const greet = (name) => console.log("Hi", name);
greet("Sara");
})();
4. Pass Globals as Parameters
✅ Improves minification and allows better testing/mocking.
(function(window, document) {
// use window and document locally
console.log(document.title);
})(window, document);
5. Prefer Arrow IIFEs for One-Liners or Simple Logic
✅ Cleaner syntax in modern JavaScript.
(() => console.log("Executed!"))();
🌍 Real-world Scenarios
- 1. Third-party libraries: Libraries like jQuery wrap all their logic in an IIFE to avoid polluting the global scope.
- 2. Script Initialization: You might initialize tooltips, carousels, or charts immediately upon page load using an IIFE.
- 3. Module Emulation (before ES6 modules): Developers used IIFEs to simulate private variables and encapsulated modules.
- 4. Analytics setup: Services like Google Analytics embed scripts that use IIFEs to track page views immediately.
- 5. Configuration Wrappers: App configuration and environment setup often use IIFEs to load data only once.
- 6. Polyfills: Many polyfills (e.g., for `Array.prototype.includes`) are wrapped in IIFEs to patch browser behavior without interference.
- 7. Preventing conflicts in browser extensions: Extension content scripts often use IIFEs to isolate their logic from the host page’s JS.
Default Parameters
🧠 Detailed Explanation
In JavaScript, default parameters allow you to set a value for a function parameter in case no argument is provided when the function is called.
This feature helps avoid undefined
values and the need to write extra logic to handle missing arguments.
The syntax is simple: you assign a value to the parameter inside the function definition using the =
sign.
function greet(name = "Guest") {
console.log("Hello, " + name);
}
- greet() → "Hello, Guest" (uses default)
- greet("John") → "Hello, John" (uses passed argument)
✅ Default parameters make your functions more flexible and reduce the chance of bugs caused by missing values.
This feature was introduced in ES6 (ECMAScript 2015) and is supported in all modern browsers.
💡 Examples
Example 1: Basic greeting function
function welcome(user = "Anonymous") {
return `Welcome, ${user}!`;
}
Output: welcome()
→ "Welcome, Anonymous!"
Example 2: Default object argument
function connect({ host = "localhost", port = 3000 } = {}) {
console.log(`Connecting to ${host}:${port}`);
}
connect(); // Connecting to localhost:3000
🔁 Alternative Concepts
Before ES6, developers used ||
to provide fallback values:
function greet(name) {
name = name || "Guest";
console.log("Hello", name);
}
❓ General Q&A
Q: What happens if a null
value is passed?
A: The default is not used. Only undefined
triggers the default.
🛠️ Technical Q&A
Q: Can you use expressions as default values?
A: Yes. Example:
function add(a, b = a * 2) {
return a + b;
}
✅ Best Practices
- Use default parameters instead of manual
if (!arg)
checks. - Default parameters should come after required ones.
- Use destructuring with defaults for object parameters.
🌍 Real-world Scenario
Default parameters are widely used in utility functions, like formatting dates, initializing user settings, or creating fallback values for API requests.
Chaining Methods (.filter().map())
🧠 Detailed Explanation
In JavaScript, chaining means calling one method after another on the same data.
The two most common array methods used together are:
.filter()
– to select specific items from an array.map()
– to transform each item in the array
You usually filter first to get only what you need, then map to change each result.
This is very useful for things like:
- Showing only completed tasks and formatting them
- Finding users with specific roles and displaying their names
Bonus: You can keep chaining more methods like .sort()
or .reduce()
if needed.
👉 Chaining = cleaner, shorter, more readable code!
💡 Examples
🎯 Example 1: Show active users' names
const users = [
{ name: "Aisha", active: true },
{ name: "Ali", active: false },
{ name: "Sana", active: true }
];
const activeUserNames = users
.filter(user => user.active)
.map(user => user.name);
console.log(activeUserNames); // ["Aisha", "Sana"]
Explanation: First we filter the users who are active, then we map to just get their names.
🎯 Example 2: Get all even numbers squared
const numbers = [1, 2, 3, 4, 5, 6];
const evenSquares = numbers
.filter(n => n % 2 === 0)
.map(n => n * n);
console.log(evenSquares); // [4, 16, 36]
Explanation: First we select even numbers with .filter()
, then square them with .map()
.
🎯 Example 3: Format emails of users above age 18
const users = [
{ email: "ali@example.com", age: 17 },
{ email: "sana@example.com", age: 21 },
{ email: "john@example.com", age: 30 }
];
const emails = users
.filter(user => user.age >= 18)
.map(user => `Email: ${user.email}`);
console.log(emails);
// ["Email: sana@example.com", "Email: john@example.com"]
Explanation: We filter users by age and then format their email for display.
🔁 Alternative Methods
You could also use a loop or reduce()
to achieve the same, but chaining offers a cleaner and declarative syntax.
❓ General Questions & Answers
Q: Why is chaining better than using loops?
A: Chaining is more concise and easier to read. It also prevents mutation of the original array.
🛠️ Technical Q&A
Q: Can you chain other methods like .sort()
or .reduce()
?
A: Yes. As long as the method returns an array or compatible data structure, you can chain it.
✅ Best Practices
- Use chaining for readability and composability.
- Avoid chaining if it becomes too long or unreadable — consider breaking into steps.
- Use arrow functions for concise logic.
🌍 Real-world Scenario
In an e-commerce app, you might use .filter()
to select in-stock items, then .map()
to format product cards, and .sort()
to rank them by popularity.
ES6 Modules: import / export
🧠 Detailed Explanation
ES6 Modules introduced a standardized way to split JavaScript code into reusable pieces, known as modules. This makes code easier to maintain, debug, and test.
With ES6, you can export variables, functions, objects, or classes from one file, and then import them into another. This helps structure your project into logical units.
- Named Exports: You can export multiple things using their names.
- Default Export: Each module can export one default thing.
Browsers now support ES6 modules natively, and build tools like Webpack or Vite handle them efficiently in larger apps.
💡 Examples
📦 Exporting from a file (mathUtils.js)
// Named exports
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// Default export
export default function multiply(a, b) {
return a * b;
}
📥 Importing in another file (main.js)
// Import named exports
import { add, subtract } from './mathUtils.js';
// Import default export
import multiply from './mathUtils.js';
console.log(add(2, 3)); // 5
console.log(subtract(5, 2)); // 3
console.log(multiply(3, 4)); // 12
📁 Explanation:
export
is used to expose parts of a module.import
is used to bring those parts into another file.- You can mix named and default exports in the same file.
🔁 Alternative Concepts
Before ES6, developers used require
and module.exports
with CommonJS in Node.js. ES6 Modules provide a more native and browser-friendly approach.
❓ General Questions & Answers
Q1: What is the difference between named and default exports?
A:
- Named exports allow you to export multiple values using their names.
- Default export is used when you want to export a single value or function as the main export of the module.
// Named
export const greet = () => {};
// Default
export default function main() {}
Q2: Can I mix default and named exports in the same file?
A: Yes. A file can have one default export and multiple named exports.
export const sum = (a, b) => a + b;
export default function multiply(a, b) {
return a * b;
}
Q3: Why should I use modules instead of putting everything in one file?
A: Modules help you keep your code organized, reusable, and easier to test and maintain.
Q4: Do I need a bundler like Webpack or Vite to use import/export?
A:
If you're using modules in the browser, make sure to add type="module"
in your script tag. For production apps, a bundler is often used for better performance and compatibility.
<script type="module" src="main.js"></script>
🛠️ Technical Q&A with Examples
Q1: How do I import both named and default exports from the same module?
A: You can import the default export followed by the named exports in curly braces.
// module.js
export default function greet() {
console.log("Hello!");
}
export const name = "React";
// main.js
import greet, { name } from './module.js';
greet(); // "Hello!"
console.log(name); // "React"
Q2: What happens if I try to import a named export that doesn't exist?
A: You'll get a runtime error or a build-time error (in strict setups).
// module.js
export const age = 25;
// main.js
import { name } from './module.js'; // ❌ Error: 'name' is not exported
Q3: How can I alias imports to avoid name conflicts?
A: Use the as
keyword to rename imports.
import { name as username } from './user.js';
console.log(username); // instead of name
Q4: Can I import a module dynamically?
A: Yes, using import()
as a function. This is useful for code-splitting or conditional imports.
async function loadModule() {
const module = await import('./utils.js');
module.default(); // calling the default export
}
Q5: Are ES6 modules tree-shakeable?
A: Yes. Most modern bundlers (like Webpack, Rollup, Vite) can remove unused code from ES6 modules automatically to optimize the bundle size.
✅ Best Practices with Examples
1. Prefer Named Exports Over Default (when exporting multiple things)
✅ Makes your code easier to refactor and auto-import in editors.
// ✅ Good
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// ❌ Avoid mixing default with many named
export default function main() { ... }
export const utils = { ... };
2. Group All Exports at the Bottom (or top) for readability
// ✅ Good
function greet() { ... }
function farewell() { ... }
export { greet, farewell };
3. Match File Names with Default Export
✅ If you have a default export, name the file accordingly.
// file: userService.js
export default function userService() { ... }
4. Import Only What You Need
✅ Helps reduce bundle size and improves clarity.
// ✅ Good
import { formatDate } from './utils.js';
// ❌ Avoid
import * as Utils from './utils.js';
5. Organize Imports by Type
✅ Start with third-party, then local, then style imports.
// ✅ Good
import React from 'react';
import { useState } from 'react';
import MyComponent from './MyComponent';
import './styles.css';
🌍 Real-world Scenarios
- 📦 Component Libraries: Shared button, card, or modal components are created in separate files and exported for reuse across apps using `export`/`import`.
- 🌐 API Services: In large apps, services like `authService.js`, `userService.js`, and `productService.js` are exported and imported into screens or hooks.
- 📁 Utility Functions: Formatters like `formatDate`, `capitalize`, or validators are organized in a `utils/` folder and reused across components.
- 🧠 Redux Store Setup: Actions and reducers are defined and exported from modules like `authSlice.js`, then imported into the main store.
- 🌍 Internationalization (i18n): Language strings can be split into different files and imported as modules into the translation setup.
- 💡 Code Splitting (Lazy Loading): ES6 modules allow dynamic `import()` statements, which are used to load parts of an app only when needed.
- 📁 Theming System: A centralized theme config (e.g., `theme.js`) is created and imported in styled-components or Tailwind configurations.
Logical OR (||) for Fallbacks
🧠 Detailed Explanation
The Logical OR operator ||
is commonly used in JavaScript to provide a fallback or default value when the first operand is falsy (like null
, undefined
, 0
, ''
, or false
).
It evaluates from left to right and returns the first truthy value it encounters. If none is found, it returns the last value.
This is useful when assigning default values in situations where a variable might be missing or undefined.
const name = userName || "Guest";
→ IfuserName
is undefined,name
becomes "Guest".const count = input || 0;
→ Ifinput
is falsy,count
defaults to 0.
This pattern is very popular in JavaScript, especially in React for rendering fallbacks in JSX.
💡 Examples
// If name is null or undefined, use "Guest"
const userName = name || "Guest";
console.log(userName); // "Guest" if name is falsy
// Example with function argument
function greet(name) {
const user = name || "Anonymous";
console.log("Hello, " + user);
}
greet(); // "Hello, Anonymous"
greet("Alex"); // "Hello, Alex"
🔁 Alternative Methods
??
(Nullish Coalescing) is safer if you only want to check for null
or undefined
(not 0
or ""
):
const title = inputTitle ?? "Default Title";
❓ General Questions & Answers
Q: What's the difference between ||
and ??
?
A: ||
returns the right-hand value if the left is falsy. ??
only returns the right-hand value if the left is null
or undefined
.
Q: When should I use ||
for fallbacks?
A: When you want to treat all falsy values (e.g., 0
, false
, ""
) as fallbacks. If that’s not the case, use ??
.
🛠️ Technical Q&A
Q: Can I use ||
with function returns?
A: Yes. If a function might return null
or undefined
, you can fallback safely.
function getConfig() {
return null;
}
const config = getConfig() || { theme: "light" };
✅ Best Practices
- Use
||
for quick fallbacks when all falsy values should be treated the same. - Use
??
if you want to preserve valid falsy values (like0
orfalse
). - Keep fallback logic concise and readable to prevent confusion.
🌍 Real-world Scenario
In a React component, you might want to show a default message when a user name is not available:
const UserGreeting = ({ name }) => {
return <h1>Welcome, {name || "Guest"}!</h1>;
};
This ensures the UI stays informative, even when some data is missing.
typeof Operator
🧠 Detailed Explanation
The typeof
operator is used in JavaScript to check the type of a value. It returns the type as a string.
It's especially helpful when writing conditions or debugging, because you can know whether a value is a string
, number
, boolean
, undefined
, function
, object
, or symbol
.
✅ You use it like this: typeof value
Here are some common results:
typeof "hello"
→"string"
typeof 123
→"number"
typeof true
→"boolean"
typeof undefined
→"undefined"
typeof function() {}
→"function"
typeof {}
→"object"
typeof null
→"object"
(this is a known bug in JavaScript)
🧠 In short, typeof
helps you understand what kind of value you are working with.
💡 Examples
console.log(typeof "OpenAI"); // "string"
console.log(typeof 123); // "number"
console.log(typeof true); // "boolean"
console.log(typeof {}); // "object"
console.log(typeof null); // "object" (quirk)
console.log(typeof function(){}); // "function"
console.log(typeof Symbol()); // "symbol"
console.log(typeof undefined); // "undefined"
🔁 Alternatives or Related Concepts
instanceof
– For checking class instancesArray.isArray()
– To check specifically if a value is an arrayObject.prototype.toString.call()
– More accurate for complex types
❓ General Q&A
Q: Why does typeof null
return "object"
?
A: It's a well-known bug in JavaScript for historical reasons. null
was represented by the null pointer, which was an object reference. It has remained for backward compatibility.
🛠️ Technical Q&A
Q: How do I check if a value is an array?
A: Use Array.isArray(value)
instead of typeof
, because typeof []
returns "object"
.
✅ Best Practices
- Use
typeof
for basic types like strings, numbers, booleans, and functions. - Don’t rely on
typeof
for arrays or null — use dedicated checks. - Always combine
typeof
with logical conditions for robust validation.
🌍 Real-world Use Case
In form validation or dynamic form builders, you may want to render different inputs depending on the data type of a value. You can use typeof
to determine what component to show.
Array.includes()
🧠 Detailed Explanation
The includes()
method is a simple and effective way to check if a certain element exists in an array. It returns true
if the value is found and false
if not.
This method is easier to read than older alternatives like indexOf()
and is especially useful for cleaner conditional checks.
It is case-sensitive when working with strings and performs strict comparison (like ===). So includes("Apple")
will not match "apple"
.
Syntax:
array.includes(valueToFind[, fromIndex])
valueToFind
: The element to search for.fromIndex
: (Optional) The position in the array at which to begin the search.
💡 Examples
// Basic usage
const fruits = ["apple", "banana", "mango"];
console.log(fruits.includes("banana")); // true
console.log(fruits.includes("grape")); // false
// With fromIndex
console.log(fruits.includes("apple", 1)); // false
// Case-sensitive check
console.log(fruits.includes("Banana")); // false
✅ It’s cleaner and more readable than older methods like indexOf()
.
🔁 Alternative Methods
array.indexOf(value) !== -1
— older wayarray.find()
— when you need more complex conditions
❓ General Questions & Answers
Q: What does includes()
return?
A: It returns true
if the value exists in the array, otherwise false
.
Q: Is includes()
case-sensitive?
A: Yes, includes("Apple")
and includes("apple")
are different.
🛠️ Technical Q&A
Q: Can includes()
check for object values?
A: It can, but only by reference:
const obj = { name: "John" };
const arr = [obj];
console.log(arr.includes(obj)); // true
console.log(arr.includes({ name: "John" })); // false (different reference)
✅ Best Practices
- Use
includes()
for simple existence checks in arrays. - Avoid using it for deep object comparison — use
find()
orsome()
instead. - Ensure your values are case-matched when working with strings.
🌍 Real-world Scenario
In a shopping cart system, use includes()
to check if a product ID already exists in the cart before adding it again.
Array some()
& every()
🧠 Detailed Explanation
In JavaScript, some()
and every()
are array methods used to test whether elements in an array satisfy a condition.
-
some()
checks if at least one element in the array passes the test. It stops iterating as soon as it finds a match. -
every()
checks if all elements pass the test. If one fails, it stops and returnsfalse
.
Both methods return a boolean
value:
true
if the condition is metfalse
otherwise
These methods are useful for quick checks or validations on an array without needing loops or manual iteration.
💡 Examples
// Example 1: some()
const numbers = [1, 2, 3, 4];
const hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // true
// Example 2: every()
const allPositive = numbers.every(num => num > 0);
console.log(allPositive); // true
🔁 Alternative Methods
filter()
can be used for similar purposes, but returns matching elements instead of a boolean.
❓ General Q&A
Q: When should I use some()
vs every()
?
A: Use some()
when you want to check if any value meets the condition. Use every()
when all values must meet the condition.
🛠️ Technical Q&A
Q: What happens with an empty array?
A: some([])
returns false
, every([])
returns true
(because there are no elements to fail the condition).
✅ Best Practices
- Keep the callback function clean and short.
- Use early return logic when possible for performance.
- Don’t mutate the original array inside the callback.
🌍 Real-world Scenario
In a shopping cart, some()
can be used to check if at least one item is on sale, and every()
to check if all items are in stock.
Object Property Shorthand
🧠 Detailed Explanation
In JavaScript, when you're creating an object and the property name is the same as the variable name, you can use object property shorthand to make your code cleaner and shorter.
Without shorthand:
const name = "Ali";
const age = 30;
const user = {
name: name,
age: age
};
With shorthand:
const user = {
name,
age
};
JavaScript understands that you mean name: name
and age: age
when you just write name
and age
inside the object.
- ✅ Makes the code shorter and more readable
- ✅ Commonly used in modern JavaScript and frameworks like React
- ❗ Only works when the variable name and the key are exactly the same
Think of it as a shortcut that lets you skip repeating yourself — helpful in keeping your code DRY (Don't Repeat Yourself).
💡 Examples
// Traditional syntax
const title = "Engineer";
const department = "Development";
const employee = {
title: title,
department: department
};
// Shorthand syntax
const employee = { title, department };
Both objects will have the same output, but the shorthand syntax is cleaner.
🔁 Alternative Concepts
If variable names and keys are different, you can't use shorthand. You must use the full syntax:
const userName = "ali";
const user = {
name: userName // full syntax needed
};
❓ General Q&A
Q: When should I use object property shorthand?
A: Use it when your variable name matches the key name in your object. It makes the code cleaner and easier to read.
Q: Is it mandatory?
A: No, it’s optional. Both forms work the same, but shorthand is preferred in modern JS codebases.
🛠️ Technical Q&A
Q: Does shorthand work inside nested objects?
A: Yes, as long as the property name matches a variable in scope. Example:
function createConfig(host, port) {
return {
server: { host, port }
};
}
✅ Best Practices
- ✅ Use shorthand for readability and clean code.
- ✅ Use full syntax when variable names differ.
- ✅ Avoid overusing shorthand in unclear contexts.
🌍 Real-world Scenario
In React, when passing props to components:
const user = { name, email, age };
<UserCard {...user} />
This pattern uses object shorthand and the spread operator to simplify prop passing.
Array Destructuring
🧠 Detailed Explanation
Array destructuring is a feature in JavaScript that lets you easily assign values from an array into individual variables.
Instead of writing:
const colors = ["red", "green", "blue"];
const first = colors[0];
const second = colors[1];
You can simply write:
const [first, second] = ["red", "green", "blue"];
✅ It’s shorter and makes the code cleaner and easier to understand.
You can also skip values using commas or set default values if some items are missing.
const [a, , c] = [1, 2, 3]; // a = 1, c = 3
const [x = 10, y = 20] = []; // x = 10, y = 20
Destructuring is commonly used in functions, loops, and React hooks like useState
.
💡 Examples
// Basic
const colors = ["red", "green"];
const [primary, secondary] = colors;
// Skipping values
const numbers = [1, 2, 3];
const [, , third] = numbers;
// Default values
const [a = 10, b = 20] = [undefined];
console.log(a); // 10
console.log(b); // 20
🔁 Alternative Concepts
- Using array indexing (e.g.,
arr[0]
) – not as clean - Object destructuring for key-value pairs
❓ General Q&A
Q: Can I destructure from nested arrays?
A: Yes! Example: const [[a], b] = [[1], 2];
gives a = 1
, b = 2
.
🛠️ Technical Q&A
Q: What happens if the array has fewer elements than variables?
A: The unmatched variables will be undefined
, unless default values are provided.
✅ Best Practices
- Use destructuring for cleaner and shorter variable assignments
- Assign default values to prevent
undefined
- Don’t overuse destructuring for deeply nested structures — readability matters
🌍 Real-world Scenario
In React, props or state values are often returned as arrays (e.g., useState
). Destructuring helps you directly extract values:
const [count, setCount] = useState(0);
Inline Truthy/Falsy Rendering
🧠 Detailed Explanation
Inline Truthy/Falsy Rendering in JavaScript (especially in React) is a concise way to conditionally display UI elements. It’s based on whether a value is truthy or falsy.
In React, you can use logical operators like &&
or ||
directly inside JSX to decide whether to show something.
- Truthy: Any value that is not
false
,0
,""
,null
,undefined
, orNaN
. - Falsy: One of the above values.
For example, if isLoggedIn
is true
, the message inside will be shown:
{isLoggedIn && <p>Welcome back!</p>}
If isLoggedIn
is false
, nothing will be rendered.
This technique is widely used in React to show loading spinners, conditionally show buttons, or hide/show sections of the UI based on state.
💡 Examples
Example 1: Show Message if Logged In
function Welcome({ isLoggedIn }) {
return (
<div>
{isLoggedIn && <p>Welcome back!</p>}
</div>
);
}
Example 2: Show Warning if Not Logged In
function AuthWarning({ isLoggedIn }) {
return (
<div>
{!isLoggedIn && <p>Please log in first.</p>}
</div>
);
}
🔁 Alternative Methods
You can use ternary operators for more complex conditions:
{isLoggedIn ? <Welcome /> : <LoginForm />}
Or fallbacks with ||
to render a default:
{username || "Guest"}
❓ General Questions & Answers
Q: Why use inline rendering?
A: It's a clean and quick way to show or hide content without writing multiple condition blocks.
Q: What is a falsy value?
A: In JavaScript, values like false
, 0
, ""
, null
, undefined
, and NaN
are considered falsy.
🛠️ Technical Q&A
Q: What happens if the value is 0
?
A: Since 0
is falsy, using {value && <Component />}
will not render the component, even if 0
is a valid input. You may want to explicitly check for null
or undefined
.
✅ Best Practices
- ✅ Use for simple one-line UI logic
- ✅ Avoid nesting too many conditions inside JSX
- ✅ Use ternary when you need an “else” alternative
- ✅ Avoid using it when the left-hand value could be 0 or empty string if it’s still valid
🌍 Real-world Scenario
In a dashboard app, you may want to show “Upgrade Now” only if the user is on a free plan:
{user.plan === "free" && <UpgradeBanner />}
Dynamic Object Keys ([key])
🧠 Detailed Explanation
In JavaScript, Dynamic Object Keys allow you to define or access object properties using variables instead of hard-coded names. This is done using square bracket notation [key]
during object creation or when updating an object.
This feature is extremely useful when the property name is not known ahead of time and must be generated dynamically — for example, from user input or inside a loop.
Dynamic keys provide flexibility in creating objects, especially for building dynamic form data, handling API responses, or mapping external data structures.
You can think of it like this: { [key]: value }
will evaluate the variable key
and use its value as the property name.
💡 Examples
// Example 1: Basic usage
const key = "username";
const user = {
[key]: "john_doe"
};
// Output: { username: "john_doe" }
// Example 2: Inside a function
function createObject(field, value) {
return {
[field]: value
};
}
const obj = createObject("email", "john@example.com");
// Output: { email: "john@example.com" }
🔁 Alternative Concepts
Without dynamic keys, you would need to use conditional logic or static keys, which makes your code less flexible:
const obj = {};
if (field === 'email') {
obj.email = 'john@example.com';
}
This approach is more verbose and less dynamic than using [field]
.
❓ General Q&A
Q: Why do we use square brackets for dynamic keys?
A: Square brackets tell JavaScript to evaluate the expression inside and use its result as the key name.
Q: Can we use dynamic keys in arrays?
A: Not directly. Arrays use numeric indexes, but you can use dynamic keys to update objects stored inside arrays.
🛠️ Technical Q&A
Q: Can I use multiple dynamic keys in one object?
const nameKey = "name";
const ageKey = "age";
const person = {
[nameKey]: "Ali",
[ageKey]: 30
};
Q: How do I use a dynamic key to update an existing object?
const user = { name: "Aisha" };
const key = "name";
user[key] = "Sara"; // updates name
✅ Best Practices
- Use dynamic keys when building forms, settings, or configurable data structures.
- Keep the key variable clearly named and scoped.
- Avoid overuse—prefer static keys when structure is fixed for readability.
🌍 Real-world Scenario
In a form builder tool, users define field names dynamically. You can create an object representing the form data using:
function handleInputChange(fieldName, value) {
formData = {
...formData,
[fieldName]: value
};
}
This way, the key depends on the user-defined field name.
Mutating vs Non-Mutating Methods
🧠 Detailed Explanation
In JavaScript, mutating and non-mutating methods define how operations affect the original data structure—usually arrays or objects.
- Mutating Methods: Directly change the original array or object. These methods modify the data in place.
- Non-Mutating Methods: Do not change the original data. Instead, they return a new modified version of the array or object.
Why it matters: In frameworks like React, using non-mutating methods is a best practice to preserve immutability. This makes your state changes predictable and easier to debug.
Think of mutating methods as writing on your notebook's only copy, while non-mutating methods are like making a photocopy, writing on the copy, and keeping the original intact.
💡 Examples
Mutating Example (Array):
const arr = [1, 2, 3];
arr.push(4); // Mutates original array
console.log(arr); // [1, 2, 3, 4]
Non-Mutating Example (Array):
const arr = [1, 2, 3];
const newArr = arr.concat(4); // Returns new array
console.log(arr); // [1, 2, 3]
console.log(newArr); // [1, 2, 3, 4]
Mutating Example (Object):
const user = { name: 'Ali' };
user.name = 'Ahmed'; // Mutates original object
Non-Mutating Example (Object):
const user = { name: 'Ali' };
const updatedUser = { ...user, name: 'Ahmed' }; // New object
Set and Map
🧠 Detailed Explanation
Set and Map are advanced data structures introduced in ES6 that offer more efficient and expressive ways to manage collections of data.
Set: A Set
is a collection of unique values. It automatically removes duplicates and can store any type of value (primitive or object).
- add(value) – Adds a value to the Set.
- has(value) – Checks if a value exists.
- delete(value) – Removes a value.
- size – Returns the number of items in the Set.
Map: A Map
is a collection of key-value pairs where keys can be of any type (not just strings or symbols like in regular objects).
- set(key, value) – Sets the value for the key.
- get(key) – Returns the value associated with the key.
- has(key) – Checks if the key exists.
- delete(key) – Removes the key-value pair.
- size – Returns the number of entries in the Map.
Summary: Use Set
when you want a collection of unique items and Map
when you need key-value pairs with any data type as keys. Both provide fast performance and clean syntax for working with collections.
💡 Examples
Set Example:
const mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(2); // Duplicate, won't be added
console.log(mySet); // Set { 1, 2 }
Map Example:
const myMap = new Map();
myMap.set('name', 'Alice');
myMap.set(123, true);
console.log(myMap.get('name')); // Alice
console.log(myMap.get(123)); // true
🔁 Alternatives
Before ES6, Arrays and plain Objects were used for similar tasks, but they lacked the unique capabilities of Set (no duplicates) and Map (non-string keys).
❓ General Q&A
Q: What's the difference between Set and Array?
A: Arrays allow duplicates, Sets don’t. Sets also have methods like has()
and delete()
for quick checks and removals.
🛠️ Technical Q&A
Q: Can Map keys be objects?
A: Yes! That’s a key advantage of Map over regular objects.
const key = { id: 1 };
const map = new Map();
map.set(key, 'value');
console.log(map.get(key)); // 'value'
✅ Best Practices
- Use
Set
when you need to store unique values. - Use
Map
when you need key-value pairs and keys can be any data type. - Use
has()
for checking existence efficiently. - Prefer Maps for large key-value stores due to better performance.
🌍 Real-World Scenario
In a contact list app, you can use a Set to store email addresses to prevent duplicates. Use a Map to store user preferences with user objects as keys.
Array.from() & Array(n).fill()
🧠 Detailed Explanation
In JavaScript, we often need to create or convert arrays. Two helpful tools for this are Array.from()
and Array(n).fill()
.
Array.from() lets you create an array from:
- 👉 Any iterable (like strings, sets, or NodeLists)
- 👉 Array-like objects (like
{ length: 5 }
) - 👉 You can even apply a function while creating the array (like mapping values)
Array(n).fill(value) creates an array with n
items and fills each item with the same value.
This is useful for initializing arrays with placeholders or repeated values.
Both methods are commonly used to generate dynamic data, placeholders, or transform values when building lists, tables, or loops in React and modern JavaScript apps.
💡 Examples
Example 1: Convert string to array
const chars = Array.from('React');
// Output: ['R', 'e', 'a', 'c', 't']
Example 2: Create range
const range = Array.from({ length: 5 }, (_, i) => i + 1);
// Output: [1, 2, 3, 4, 5]
Example 3: Create placeholder array
const zeros = Array(3).fill(0);
// Output: [0, 0, 0]
🔁 Alternative Concepts
for
loop to build arraysmap()
on manually constructed arraysArray.apply(null, {length: n})
(older method)
❓ General Q&A
Q: What’s the difference between Array.from()
and Array.of()
?
A: Array.from()
turns iterable/array-like objects into arrays. Array.of()
creates an array from arguments.
🛠️ Technical Q&A
Q: How do I create an array of 10 elements with their index values?
Array.from({ length: 10 }, (_, i) => i);
Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
✅ Best Practices
- Use
Array.from()
for converting NodeLists or Sets to arrays - Use
Array(n).fill()
to initialize arrays for mapping later - Prefer
Array.from({length: n}, fn)
when you want an array filled with computed values
🌍 Real-world Use Cases
- Creating skeleton loaders or placeholders
- Generating numeric ranges
- Converting HTML collections to arrays for DOM manipulation
- Generating random test data
- Duplicating default form field sets
- Displaying static stars or ratings
- Running parallel API calls with Promise.all from array of inputs
Debouncing & Throttling
🧠 Detailed Explanation
Debouncing and Throttling are techniques used in JavaScript to control how often a function runs — especially useful when dealing with frequent events like typing, scrolling, or resizing the window.
👉 Debounce: Waits for a break in the event firing. It only runs the function *after* a specified time has passed since the last event.
Use case: When the user stops typing in a search box, make an API call.
👉 Throttle: Ensures a function runs at most once in every X milliseconds, even if the event fires multiple times.
Use case: Limit how often a scroll handler runs while scrolling.
These methods improve performance and user experience by avoiding too many function calls too quickly.
✅ Implementation
Debounce Function: Limits how often a function is called by waiting for a pause in events.
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
Usage Example: Debouncing a search input field
const handleSearch = debounce((query) => {
fetch(`/api/search?q=${query}`).then(...);
}, 300);
searchInput.addEventListener("input", (e) => {
handleSearch(e.target.value);
});
Throttle Function: Limits a function to run only once every set interval.
function throttle(fn, interval) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= interval) {
lastCall = now;
fn.apply(this, args);
}
};
}
Usage Example: Throttling scroll events
const logScroll = throttle(() => {
console.log("Scroll event at", window.scrollY);
}, 500);
window.addEventListener("scroll", logScroll);
✅ Use debounce
for events like typing (input), and throttle
for frequent events like scrolling, resizing, or mouse movement.
💡 Examples
Example 1: Debouncing - Search Input
function debounce(fn, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), delay);
};
}
function fetchResults(query) {
console.log("Searching for:", query);
}
const searchInput = document.getElementById("search");
searchInput.addEventListener("input", debounce((e) => {
fetchResults(e.target.value);
}, 500));
Explanation: When the user types, it waits 500ms after the last keystroke before calling fetchResults
. This avoids firing the API on every keystroke.
Example 2: Throttling - Window Scroll
function throttle(fn, interval) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= interval) {
lastCall = now;
fn.apply(this, args);
}
};
}
function trackScroll() {
console.log("Scroll position:", window.scrollY);
}
window.addEventListener("scroll", throttle(trackScroll, 300));
Explanation: The trackScroll
function is only called once every 300ms even if the user scrolls continuously.
Example 3: Debounced Button Click
const button = document.getElementById("save-btn");
const save = debounce(() => {
console.log("Data saved!");
}, 1000);
button.addEventListener("click", save);
Explanation: Clicking the button repeatedly triggers save
only after 1 second of no clicking.
🔁 Alternative Methods
- ✅ Use
lodash.debounce
orlodash.throttle
for easy implementations. - ✅ Consider
requestAnimationFrame
for animations or UI updates.
❓ General Questions & Answers
Q1: What is the difference between debounce and throttle?
A:
- Debounce
waits for a pause in user activity before running the function. It’s great for search boxes or autocomplete.
- Throttle
ensures the function runs at most once in a defined interval, no matter how many times the event fires. Perfect for scroll or resize events.
Q2: When should I use debounce?
A: Use debounce when you only want to trigger a function after the user has stopped typing, moving, or interacting — such as filtering data after typing or auto-saving a form.
Q3: When should I use throttle?
A: Use throttle when you want to limit how often a function runs over time — like updating a scroll progress bar or handling window resizing smoothly without overloading the browser.
Q4: Are debounce and throttle built into JavaScript?
A: No. They are utility functions you have to create yourself or import from a library like Lodash (_.debounce
and _.throttle
).
Q5: Can I use debounce and throttle with React components?
A: Yes! You can use them inside useEffect
or event handlers like onChange
or onScroll
. Just make sure to clean up with clearTimeout
or clearInterval
in some cases to avoid memory leaks.
🛠️ Technical Q&A with Examples
Q1: How do I debounce an input change in JavaScript?
A: Use setTimeout
to delay the execution and clearTimeout
to reset the delay.
let timer;
function handleChange(e) {
clearTimeout(timer);
timer = setTimeout(() => {
console.log("Search value:", e.target.value);
}, 500);
}
Q2: How to debounce a React input using a custom hook?
A: Here's a reusable debounce hook:
import { useState, useEffect } from 'react';
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debounced;
}
Q3: How does throttle work under the hood?
A: Throttle uses a timestamp or a cooldown flag to ensure a function only runs once every X milliseconds.
function throttle(fn, limit) {
let inThrottle = false;
return function () {
if (!inThrottle) {
fn();
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
Q4: What's the difference in behavior when scrolling?
A:
- Debounce waits until scrolling ends to execute a function (e.g., "show scroll to top" button).
- Throttle continuously runs at intervals during scrolling (e.g., updating scroll progress bar).
Q5: What library should I use for debounce/throttle?
A: Lodash
is widely used. Example:
import debounce from 'lodash.debounce';
const handleSearch = debounce((val) => {
console.log("Searching for", val);
}, 300);
✅ Best Practices with Examples
1. Use debounce for user input fields (e.g., search)
✅ This avoids unnecessary API calls or computations on every keystroke.
// Inside React component
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 500);
useEffect(() => {
if (debouncedQuery) {
fetchData(debouncedQuery);
}
}, [debouncedQuery]);
2. Use throttle for continuous events (e.g., scroll, resize)
✅ Ensures performance efficiency while still responding to user actions.
const throttledScroll = throttle(() => {
console.log("User is scrolling...");
}, 200);
window.addEventListener('scroll', throttledScroll);
3. Cleanup timers or listeners in useEffect
✅ Prevents memory leaks when components unmount.
useEffect(() => {
const handler = debounce(() => {
console.log("Window resized");
}, 300);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
4. Avoid inline debounce/throttle inside render
❌ This creates a new function on every render and breaks optimization.
// ❌ Not good
<input onChange={debounce((e) => {...}, 300)} />
// ✅ Better
const handleChange = debounce((e) => {...}, 300);
<input onChange={handleChange} />
5. Tune delay duration for best UX
⏱️ Short delays feel snappy, long delays may frustrate users. Test what fits best (300–500ms is common).
6. Use libraries for production
📦 Lodash's debounce
and throttle
are reliable and well-tested. Don’t reinvent the wheel unless needed.
🌍 Real-World Use Cases
- 1. Live Search: Debouncing is commonly used to delay the search query while a user types in the input field, sending the request only after a pause (e.g., 300ms).
- 2. Window Resize Handling: Throttling ensures the function attached to window resize doesn’t fire hundreds of times per second.
- 3. Infinite Scrolling: Throttle the scroll event listener to fetch new data only after a fixed interval, improving performance.
- 4. Form Validation: Debounce validations to avoid checking or hitting APIs after every keystroke in a form field.
- 5. Mouse Movement Tracking: Throttle the event handler when tracking mouse movement for performance reasons.
- 6. Auto-save Draft: Debounce auto-save logic in a text editor so it saves only when the user pauses typing.
- 7. Button Click Rate Limiting: Throttle button clicks to prevent multiple submissions (like “Buy Now” or “Submit” buttons).
About React
JSX & Rendering Elements
🧠 Detailed Explanation
JSX (JavaScript XML) is a way to write what looks like HTML inside your JavaScript code — but it’s not actually HTML. It’s just a special syntax that React understands.
Normally in JavaScript, you would write code to create elements like this:
React.createElement('h1', null, 'Hello');
But with JSX, you can write it like this instead:
<h1>Hello</h1>
It’s much cleaner and easier to read — especially when your UI has many elements.
JSX allows you to mix HTML-like code with JavaScript, so you can do things like:
const name = 'Ali';
const greeting = <h1>Hello, {name}!</h1>;
In the example above, {name}
is a JavaScript variable being used inside the JSX.
JSX is not required to use React, but it makes development faster and more fun.
Think of JSX as writing your UI in a way that feels natural, while React takes care of turning it into something the browser can understand.
💡 Examples
1. Basic JSX Element
const element = <h1>Hello, React!</h1>;
Explanation: This creates an <h1>
heading with the text “Hello, React!”
2. Rendering JSX to the DOM
ReactDOM.render(
<h1>Welcome to React</h1>,
document.getElementById('root')
);
Explanation: This code tells React to show the heading inside a div with the ID “root”.
3. Using JavaScript Expressions in JSX
const name = 'Ali';
const element = <h2>Hello, {name}!</h2>;
Output: <h2>Hello, Ali!</h2>
Explanation: Curly braces { }
are used to add JavaScript values inside JSX.
4. JSX with Multiple Elements
const element = (
<div>
<h1>Title</h1>
<p>This is a paragraph.</p>
</div>
);
Explanation: JSX must have one parent tag. Here, all elements are wrapped in a <div>
.
5. JSX with React Fragments
const element = (
<>
<h1>Hello</h1>
<p>No need for a <div> here</p>
</>
);
Explanation: <></>
is a short version of <React.Fragment>
, used to group elements without adding extra HTML.
🔁 Alternative Methods
JSX is optional — you can use React.createElement
instead:
React.createElement('h1', null, 'Hello, world!');
But JSX is recommended for readability and maintainability.
❓ General Q&A
Q1: Is JSX the same as HTML?
A: No, JSX only looks like HTML, but it's not exactly the same. JSX is a syntax extension for JavaScript that allows you to write UI elements in a readable way. Behind the scenes, JSX gets converted into JavaScript code using a tool like Babel.
Q2: Do I have to use JSX in React?
A: No, JSX is optional. You can use React.createElement()
to create elements. However, JSX is widely used because it makes your code more readable and cleaner.
Q3: Why do I need to wrap everything in one parent tag?
A: JSX must return a single element. If you want to return multiple elements, wrap them in a parent element like <div>
or use React Fragments (<></>
) to avoid adding unnecessary tags in your HTML.
Q4: Can I use JavaScript in JSX?
A: Yes! You can use JavaScript expressions (like variables, functions, or logic) inside JSX using curly braces { }
. For example:
const name = \"Sara\";
const greeting = <h1>Hello, {name}!</h1>;
Q5: What happens to JSX in the browser?
A: Browsers don’t understand JSX directly. It must be converted (or compiled) into regular JavaScript using tools like Babel. That’s why most React projects use a build tool like Webpack or Vite.
🛠️ Technical Q&A with Solutions & Examples
Q1: Why does JSX allow only one parent element?
A: JSX must return a single parent element because React's render method expects one root element per component.
❌ Wrong:
return (
<h1>Hello</h1>
<p>Welcome</p>
);
✅ Right:
return (
<>
<h1>Hello</h1>
<p>Welcome</p>
</>
);
Q2: How do I conditionally render an element in JSX?
A: You can use JavaScript logic like if
statements or ternary operators inside JSX.
const isLoggedIn = true;
return (
<div>
{isLoggedIn ? <p>Welcome Back!</p> : <p>Please Log In</p>}
</div>
);
Q3: Can I use functions inside JSX?
A: Yes. You can call functions or use variables within { }
inside JSX.
function greet(name) {
return `Hello, ${name}`;
}
return <h1>{greet('Sara')}</h1>;
Q4: Why does my JSX not render variables?
A: You must use curly braces { }
to embed variables or expressions.
// ❌ This will not work
<h1>name</h1>
// ✅ This will work
<h1>{name}</h1>
Q5: How do I render a list of items in JSX?
A: Use the map()
function to loop over an array and render elements.
const fruits = ['Apple', 'Banana', 'Orange'];
return (
<ul>
{fruits.map(fruit => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
💡 Tip: Always add a unique key
when rendering lists.
✅ Best Practices with Examples
1. Always wrap multiple elements with a parent element or fragment
// ❌ This will throw an error
return (
<h1>Title</h1>
<p>Paragraph</p>
);
// ✅ Use a parent element
return (
<div>
<h1>Title</h1>
<p>Paragraph</p>
</div>
);
// ✅ Or use fragments
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
2. Use camelCase for JSX attributes
// ❌ Incorrect
<button onclick=\"handleClick\">Click</button>
// ✅ Correct
<button onClick={handleClick}>Click</button>
3. Use { }
for dynamic values or expressions
const name = 'Ali';
// ✅ Correct
<h2>Hello, {name}</h2>
4. Add a unique key when rendering lists
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
Why? This helps React track what changed and improves performance.
5. Self-close tags when they don’t need content
// ❌ Don't write:
<br></br>
// ✅ Instead write:
<br />
// Other examples:
<img src=\"logo.png\" />
<input type=\"text\" />
6. Keep JSX readable
// ✅ Good: clear structure
return (
<div className=\"profile\">
<h1>User Profile</h1>
<p>Welcome back!</p>
</div>
);
Split complex JSX into smaller components to keep things clean and manageable.
🌍 Real-World Scenario
JSX makes it easier to write UI components that update dynamically based on user actions, like showing a welcome message after login:
const Welcome = ({ name }) => <h1>Welcome, {name}!</h1>;
Functional Components vs Class Components
🧠 Detailed Explanation
In React, you create UI using something called components. There are two main ways to write them:
- Class Components: These use the
class
keyword (like in OOP). They were the old standard and can use features like state and lifecycle methods. - Functional Components: These are just JavaScript functions. They used to be simple, but now with hooks, they can do everything class components can — and more!
Today, most developers use functional components because they are easier to write, understand, and maintain.
Think of it like this:
- 🧑🏫 Class components = the traditional way, more code
- 🧑💻 Functional components = the modern way, less code, more power
Thanks to React Hooks (like useState
and useEffect
), functional components are now the preferred choice in almost all React projects.
💡 Examples
Example 1: Functional Component (Modern)
import React from 'react';
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
Explanation: This is a simple function that takes props
and returns a greeting. It’s clean and easy to read.
Example 2: Class Component (Old Style)
import React, { Component } from 'react';
class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
Explanation: This uses the class
keyword and has a render()
method. You also use this.props
instead of just props
.
Example 3: Functional Component with State (Using Hooks)
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click Me</button>
</div>
);
}
Explanation: useState
allows this functional component to have its own state, just like a class component would!
Example 4: Class Component with State
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click Me
</button>
</div>
);
}
}
Explanation: Same result as the functional version, but with more boilerplate code like constructor, this.state
, and this.setState()
.
🔁 Alternative Concepts
Functional components are now the recommended approach. Use useState
and useEffect
hooks instead of class lifecycle methods.
❓ General Q&A
Q1: What is the difference between functional and class components?
A:
Functional components are just JavaScript functions that return JSX. They are simple, shorter, and easier to understand.
Class components use ES6 class syntax and need a render()
method. They used to be the only way to handle state, but now with Hooks, functional components can do it too.
Q2: Which one should I use in new projects?
A: You should use functional components. They are the modern way of building React apps. They are less code-heavy, easier to test, and support Hooks, which make your logic reusable.
Q3: Are class components going away?
A: No, class components are still supported in React. But most new features and tutorials focus on functional components because they're simpler and more powerful with Hooks.
Q4: Can both component types use props?
A:
Yes! Both functional and class components can receive and use props. The only difference is:
👉 In a functional component, you use: props.name
👉 In a class component, you use: this.props.name
Q5: Why do developers prefer functional components?
A: Because they are:
- ✅ Easier to read and write
- ✅ Require less boilerplate code
- ✅ Work great with Hooks for handling state and effects
🛠️ Technical Q&A with Solutions & Examples
Q1: How do I manage state in functional vs class components?
A: Functional components use the useState
hook, while class components use this.state
and this.setState()
.
// Functional
const [count, setCount] = useState(0);
setCount(count + 1);
// Class
this.state = { count: 0 };
this.setState({ count: this.state.count + 1 });
Q2: How do I run code when the component is loaded (like componentDidMount)?
A: In class components, you use componentDidMount()
. In functional components, you use useEffect()
with an empty dependency array.
// Functional
useEffect(() => {
console.log('Component mounted');
}, []);
// Class
componentDidMount() {
console.log('Component mounted');
}
Q3: How do I access props in both types?
A: Functional components receive props
directly as an argument. Class components use this.props
.
// Functional
function Greet(props) {
return <h1>Hello, {props.name}</h1>;
}
// Class
class Greet extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Q4: Can I reuse logic between components?
A: Yes! With functional components, you can create custom hooks to share logic. With class components, you'd use higher-order components (HOCs) or render props, which are more complex.
// Custom hook example
function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount(c => c + 1);
return { count, increment };
}
Q5: What’s the performance difference?
A: Functional components are often faster and lighter. With fewer lines of code and fewer lifecycle complications, they simplify re-renders and memory usage. Hooks like useMemo
and React.memo
give you extra optimization control.
✅ Best Practices with Examples
1. Prefer functional components with hooks
Functional components are shorter, cleaner, and more modern. You should avoid class components in most new projects.
// ✅ Functional
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
// ❌ Class (more complex)
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}
2. Use hooks to manage state and side effects
// ✅ useState + useEffect
import { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <p>Timer: {seconds}</p>;
}
Why? This makes code cleaner and removes the need for componentDidMount
, componentWillUnmount
, etc.
3. Use arrow functions and ES6+ syntax
This avoids issues with this
and makes the code easier to read.
// ✅ Functional
const Button = ({ label }) => <button>{label}</button>;
4. Extract reusable logic into custom hooks
// ✅ Custom Hook
function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount(c => c + 1);
return { count, increment };
}
// Usage
function Counter() {
const { count, increment } = useCounter();
return (
<div>
<p>{count}</p>
<button onClick={increment}>Add</button>
</div>
);
}
Why? Hooks allow you to keep components small and logic clean.
5. Keep components small and focused
Each component should do one thing (like render a button, or a form). If it’s doing too much, break it into smaller parts.
// ✅ One component per purpose
function Header() { return <h1>Site Header</h1>; }
function Footer() { return <footer>All rights reserved</footer>; }
🌍 Real-world Scenario
Most modern React apps use only functional components. For example, a user login form can be written as a simple function using useState
to manage input fields.
Props and State
🧠 Detailed Explanation
In React, we build our user interfaces using components. To make these components useful, we pass and manage data inside them using two important tools:
- Props = short for “properties” — like function parameters. They are used to send data from one component to another (usually parent to child).
- State = a built-in way to store changing data inside a component — like tracking user input or counting clicks.
🔁 Props flow one way → from parent to child.
🧠 State lives inside the component and can be updated when something changes.
Think of it like this:
- 💌 Props: Information passed from a parent to a child — like sending a message.
- 🧠 State: The component’s own memory — it remembers things like "How many times was this button clicked?"
Example: A counter app passes a label using props (like "Like") and keeps track of clicks using state.
⚠️ Important: You can’t change props inside a child component — they are read-only! If you want to change something, use state.
💡 Examples
📦 Example 1: Using Props
// Parent Component
function App() {
return <Greeting name="Ali" />;
}
// Child Component
function Greeting(props) {
return <h2>Hello, {props.name}!</h2>;
}
Explanation: The parent passes a value (name="Ali") to the child component. The child receives it as props.name
and displays "Hello, Ali!"
🔁 Example 2: Using State
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Explanation: This component tracks how many times a button is clicked using useState
. Every time you click the button, count
updates, and the UI re-renders.
🔄 Example 3: Combine Props + State
function LikeButton({ label }) {
const [likes, setLikes] = useState(0);
return (
<button onClick={() => setLikes(likes + 1)}>
{label} 👍 {likes}
</button>
);
}
// Usage
<LikeButton label="Like Post" />
Explanation: label
is passed as a prop, while likes
is managed by state. Together, they form an interactive button.
⚠️ Example 4: Trying to change a prop (❌ Wrong Way)
function WrongExample(props) {
// ❌ Don't do this!
props.name = "Sara"; // Error: props are read-only
}
Correct Way: If you want to change a value, it should be managed through state
, not props.
🔁 Alternative Concepts
You can use global state managers like Redux or Zustand when you need to share state between components deeply in the tree. But for simple cases, local useState
and props are best.
❓ General Q&A
Q1: What is the difference between props and state?
A:
Props are like input values passed from one component (usually the parent) to another (child). They're used to send information and cannot be changed inside the receiving component.
State is data that belongs to a component itself. It can change over time, and when it does, the UI updates to reflect the new state.
Q2: Can props be changed inside a component?
A: No. Props are read-only. If you try to change a prop directly, React will show a warning. If you need to change data, manage it in the parent as state and pass a function down through props to update it.
Q3: When should I use props, and when should I use state?
A:
Use props when you want to send data to a component from the outside.
Use state when a component needs to manage and update its own data (like a counter, input field, or toggle).
Q4: What happens when state changes?
A: When you update the state using a setter like setCount
, React will re-render the component so that the UI matches the new state.
Q5: Can I use both props and state in the same component?
A: Yes, absolutely! You often use props to show something passed in, and state to handle interaction or changes. For example, a button label might come from props, while the number of clicks is tracked in state.
🛠️ Technical Q&A with Solutions & Examples
Q1: How do I update the state when a user types in an input field?
A: Use the useState
hook to store the input value and update it on each change.
function TextInput() {
const [text, setText] = useState("");
return (
<div>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<p>You typed: {text}</p>
</div>
);
}
✅ Solution: The input's value is connected to state, and onChange
keeps it updated live.
Q2: How can I pass a value from child to parent using props?
A: You can't directly pass data up, but you can pass a callback function from parent to child via props.
// Parent
function Parent() {
const [name, setName] = useState("");
const handleNameChange = (newName) => {
setName(newName);
};
return (
<div>
<Child onNameChange={handleNameChange} />
<p>Name: {name}</p>
</div>
);
}
// Child
function Child({ onNameChange }) {
return (
<input
type="text"
onChange={(e) => onNameChange(e.target.value)}
/>
);
}
✅ Solution: The child notifies the parent by calling a function passed via props.
Q3: What happens if I mutate state directly?
A: React won't know the state changed and your UI won’t update. Always use the state setter (like setState
or setCount
).
// ❌ Don't do this
state.count = 5;
// ✅ Do this instead
setCount(5);
Q4: How do I render a list using props?
A: You can pass an array through props and use map()
to display each item.
function FruitList({ fruits }) {
return (
<ul>
{fruits.map(fruit => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}
// Usage:
<FruitList fruits={['Apple', 'Banana', 'Orange']} />
Q5: Can I use the same value in both props and state?
A: Yes, but be careful. If you're going to modify it, keep it in state. You can initialize state with a prop if needed:
function Profile({ initialName }) {
const [name, setName] = useState(initialName);
return <p>Welcome, {name}</p>;
}
✅ Tip: Only use props to initialize state — don't keep them permanently in sync without side effects like useEffect
.
✅ Best Practices with Examples
1. Use props to make components reusable
Instead of hardcoding content, use props so the component can display different data.
// ✅ Reusable
function Button({ label }) {
return <button>{label}</button>;
}
// Usage:
<Button label="Submit" />
<Button label="Cancel" />
2. Keep state as local as possible
Only use state where you need to. Don’t keep everything in a parent if it’s only used in a child.
// ✅ Local state
function Toggle() {
const [on, setOn] = useState(false);
return <button onClick={() => setOn(!on)}>
{on ? "ON" : "OFF"}
</button>;
}
3. Avoid duplicating props in state
Don’t store props in state unless you need to change them. Otherwise, just use the prop directly.
// ❌ Bad: copying props into state
const [value, setValue] = useState(props.initialValue);
// ✅ Better: use props directly or update via a callback
return <p>{props.initialValue}</p>;
4. Use default props or fallback values
function Greeting({ name = "Guest" }) {
return <h2>Hello, {name}</h2>;
}
Why? It avoids showing “undefined” if no prop is passed.
5. Pass functions as props for child-to-parent communication
// Parent
function App() {
const handleClick = () => alert("Clicked from child!");
return <Child onClick={handleClick} />;
}
// Child
function Child({ onClick }) {
return <button onClick={onClick}>Click Me</button>;
}
Why? This allows the parent to control what happens when the child triggers an action.
6. Use meaningful state names
Make sure your state variables clearly describe what they hold.
// ✅ Good
const [isLoggedIn, setIsLoggedIn] = useState(false);
// ❌ Bad
const [flag, setFlag] = useState(false);
7. Use controlled components for form elements
function NameInput() {
const [name, setName] = useState("");
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
Why? This gives you full control over input values and makes validation easier.
🌍 Real-world Scenario
In a to-do list app:
- State: Tracks list of tasks and completion status.
- Props: Each task component receives the task text and status via props from the main list.
Handling Events
🧠 Detailed Explanation
In a regular website, when you want to respond to a user action (like a button click), you use events like onclick
in HTML.
In React, the concept is the same — but the way you write it is a little different:
- 👉 You use camelCase event names like
onClick
,onChange
, andonSubmit
. - 👉 You pass a function as the event handler — not a string.
For example, to handle a button click in React, you do this:
function MyButton() {
function sayHello() {
alert("Hello!");
}
return <button onClick={sayHello}>Click Me</button>;
}
✅ This is better than writing JavaScript in your HTML like old-school web pages. React keeps your code clean and organized.
You can handle many types of events in React, just like in HTML:
onClick
— when a button or link is clickedonChange
— when an input or dropdown changesonSubmit
— when a form is submittedonMouseEnter
,onKeyDown
, etc.
🧠 React Events = JavaScript functions that run when something happens on the screen.
💡 Examples
Example 1: Button Click
function SayHi() {
function handleClick() {
alert("Hi there!");
}
return <button onClick={handleClick}>Say Hi</button>;
}
Explanation: When the user clicks the button, the handleClick
function runs and shows an alert.
Example 2: Updating State on Button Click
function Counter() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
Explanation: Each time the button is clicked, the count
state is increased using setCount
.
Example 3: Getting Input Value on Change
function NameInput() {
const [name, setName] = useState("");
function handleChange(event) {
setName(event.target.value);
}
return (
<div>
<input type="text" onChange={handleChange} />
<p>You typed: {name}</p>
</div>
);
}
Explanation: When the user types, the input triggers onChange
, and the state updates live with the input value.
Example 4: Prevent Form Refresh on Submit
function SimpleForm() {
const [email, setEmail] = useState("");
function handleSubmit(e) {
e.preventDefault(); // stops the page from reloading
alert("Submitted: " + email);
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
onChange={(e) => setEmail(e.target.value)}
value={email}
/>
<button type="submit">Submit</button>
</form>
);
}
Explanation: preventDefault()
stops the form from refreshing the page. The form still behaves properly.
Example 5: Passing Custom Data in Event
function GreetButton({ name }) {
const greet = (person) => {
alert("Hello " + person);
};
return <button onClick={() => greet(name)}>Greet</button>;
}
// Usage:
<GreetButton name="Aisha" />
Explanation: The event calls a custom function with the value passed via props.
🔁 Alternatives
You can use inline functions for quick actions:
<button onClick={() => doSomething()}>
But it’s better to use named functions for readability and reusability.
❓ General Questions & Answers
Q1: How are events different in React vs HTML?
A:
In HTML, you might write:
<button onclick="myFunction()">
In React, you write:
<button onClick={myFunction}>
React uses camelCase event names (like onClick
, onChange
) and you pass a function reference, not a string.
Q2: Why does my function run when the page loads instead of when I click?
A:
You're accidentally calling the function instead of passing it.
❌ onClick={myFunction()}
→ This runs immediately.
✅ onClick={myFunction}
or onClick={() => myFunction()}
→ This waits for the click.
Q3: Can I pass values to an event handler in React?
A: Yes! Use an arrow function to pass arguments:
<button onClick={() => handleClick('React')} />
This way you can call the function with a specific value when the event occurs.
Q4: What is event.preventDefault()
and when should I use it?
A: It's used to stop the default browser behavior. For example, when a form is submitted, the page reloads by default. You can stop that with:
function handleSubmit(e) {
e.preventDefault(); // stops page reload
}
Q5: What are the most common events in React?
A:
🔹 onClick
– when a button is clicked
🔹 onChange
– when input or select changes
🔹 onSubmit
– when a form is submitted
🔹 onMouseEnter
– when the mouse enters an element
🔹 onKeyDown
– when a key is pressed
🛠️ Technical Q&A with Examples
Q1: How do I pass arguments to an event handler?
A: Use an arrow function inside the event to pass the argument.
function DeleteItem({ id }) {
const handleDelete = (itemId) => {
alert(`Deleted item #${itemId}`);
};
return (
<button onClick={() => handleDelete(id)}>Delete</button>
);
}
✅ Tip: Don’t call the function directly — wrap it in () =>
to delay execution until the event fires.
Q2: Why isn’t my input updating the UI?
A: You need to use state to store the input value and onChange
to update it.
function InputField() {
const [text, setText] = useState("");
return (
<input
value={text}
onChange={(e) => setText(e.target.value)}
/>
);
}
✅ Solution: Without value
and onChange
, React can't control the input.
Q3: How do I handle form submission without reloading the page?
A: Use event.preventDefault()
inside the onSubmit
handler.
function MyForm() {
const [email, setEmail] = useState("");
const handleSubmit = (e) => {
e.preventDefault(); // stop page reload
alert("Form submitted: " + email);
};
return (
<form onSubmit={handleSubmit}>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
}
Q4: How do I handle multiple buttons with different actions?
A: Use different handlers or a single function that accepts a parameter.
function ActionButtons() {
const handleAction = (type) => {
alert("Action: " + type);
};
return (
<>
<button onClick={() => handleAction("save")}>Save</button>
<button onClick={() => handleAction("cancel")}>Cancel</button>
</>
);
}
Q5: Can I add custom logic to a click handler?
A: Yes! Event handlers can contain any logic — conditions, API calls, etc.
function LikeButton() {
const [likes, setLikes] = useState(0);
const handleLike = () => {
if (likes >= 10) {
alert("You reached the max likes!");
return;
}
setLikes(likes + 1);
};
return <button onClick={handleLike}>Like ({likes})</button>;
}
✅ Best Practices with Examples
1. Use named functions for clarity
✅ Improves readability and makes debugging easier.
// ✅ Good
function handleClick() {
alert("Clicked!");
}
return <button onClick={handleClick}>Click</button>;
// ❌ Avoid inline logic if it's complex
<button onClick={() => {
console.log("Something");
alert("Clicked!");
}}>Click</button>
2. Prevent default behavior in forms
✅ Avoid full-page reload when submitting forms.
function handleSubmit(e) {
e.preventDefault(); // stop default form action
// do something
}
3. Keep business logic outside JSX
✅ Keep your JSX clean by separating event logic.
function LoginButton() {
const handleLogin = () => {
// logic to log in
};
return <button onClick={handleLogin}>Log In</button>;
}
4. Use state when the UI needs to respond to events
✅ Let state changes automatically trigger UI updates.
const [likes, setLikes] = useState(0);
const handleLike = () => {
setLikes(likes + 1);
};
5. Pass arguments correctly to handlers
✅ Use arrow functions to avoid accidental early execution.
<button onClick={() => doSomething(id)}>Do It</button>
6. Avoid inline arrow functions if performance matters
⚠️ Especially in large lists or performance-sensitive components.
// ✅ Memoized handler with useCallback
const handleAction = useCallback(() => {
// your logic
}, []);
7. Use consistent naming like handleX
✅ Makes your code predictable and team-friendly.
handleClick, handleChange, handleSubmit, handleDelete
🌍 Real-world Scenario
In a shopping cart, every “Add to Cart” button has an onClick
event that updates the cart state with the selected product.
📋 Lists and Keys in React
🧠 Detailed Explanation
In React, when you want to show a list of items (like a list of names, products, or messages), you usually use JavaScript’s map()
function.
But React also needs a special prop called key
for each item in the list.
- ✅ Lists are created using
map()
- 🔑 Keys help React keep track of each item
Why use keys? They help React figure out what changed, added, or removed — so it updates the screen faster and more accurately.
Example: If you delete an item from the middle of a list, React uses keys to know which one to remove — without touching the others.
Good Key: A unique id
from a database or API.
Not recommended: Using the index of the array, especially if the list changes.
In short: Lists show multiple things on screen, and keys help React manage those things efficiently.
💡 Examples
Example 1: Basic List Rendering with Index as Key
const fruits = ["Apple", "Banana", "Cherry"];
function FruitList() {
return (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
);
}
Explanation: This will render each fruit inside a <li>
. Index is used as the key. It works but can lead to bugs if the list changes order.
Example 2: Using Unique ID as Key (Recommended)
const users = [
{ id: 101, name: "Aisha" },
{ id: 102, name: "Bilal" },
{ id: 103, name: "Zoya" }
];
function UserList() {
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Explanation: Using a unique ID (like from a database) helps React track and update the list efficiently, even when items are added or removed.
Example 3: Dynamic To-Do List
const todos = [
{ id: 1, task: "Buy groceries" },
{ id: 2, task: "Call mom" },
{ id: 3, task: "Finish homework" }
];
function TodoList() {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.task}</li>
))}
</ul>
);
}
Explanation: This is how real-world apps manage dynamic lists like to-dos, notifications, or messages.
Example 4: Why Not to Use Index
// ❌ Not good if items can be reordered or deleted
{items.map((item, index) => (
<li key={index}>{item.name}</li>
))}
Problem: If items change position, React might update the wrong items because the index changes too.
🔁 Alternatives
If your data comes from a database or API, prefer using a unique identifier (like id
) instead of the index:
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
❓ General Questions & Answers
Q1: Why do we need a “key” when rendering lists in React?
A:
The key
helps React identify which items have changed, been added, or removed.
Without a unique key, React might re-render the wrong components or cause unexpected behavior.
Keys make React updates faster and more predictable.
Q2: Can I use the array index as a key?
A: Yes, but it’s **not recommended** if the list can change (items added, removed, or reordered). Using index is safe only for static lists. Otherwise, React might get confused and update the wrong element.
Q3: What happens if I don’t provide a key at all?
A:
React will show a warning in the console:
Warning: Each child in a list should have a unique "key" prop.
Without keys, performance will suffer and the UI might break unexpectedly.
Q4: What can I use as a key?
A:
🔹 A unique id
from your data (like database ID)
🔹 A unique string (like a slug or name, if guaranteed unique)
❌ Don’t use random values or Math.random()
— they change on every render.
Q5: Does the key get passed as a prop?
A:
No, the key is only used **internally by React** and is not accessible inside your component’s props.
If you need to access it, pass it as a separate prop (e.g., id
).
🛠️ Technical Q&A with Examples
Q1: How do I render a dynamic list of items in React?
A: Use the map()
function to loop through an array and return a JSX element for each item.
const fruits = ["Apple", "Banana", "Cherry"];
function FruitList() {
return (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
);
}
✅ Tip: Use a unique key instead of index if available.
Q2: What’s a better way to use keys if I have objects?
A: Use a unique identifier from the object like an id
.
const users = [
{ id: 1, name: "Aisha" },
{ id: 2, name: "John" },
];
function UserList() {
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
✅ Reason: It avoids re-rendering the wrong element when data changes.
Q3: Can I generate keys using Math.random()
or Date.now()
?
A: No. This will create a new key on every render, making React rebuild the entire list unnecessarily. It defeats the purpose of using keys for performance optimization.
Q4: What happens when keys are not unique?
A: React might confuse elements and apply changes to the wrong component. This can cause bugs like incorrect animations, focus issues, or wrong state updates.
Q5: Can I reuse the same component in a list with different props?
A: Yes, just make sure the key is unique to each instance.
function Greeting({ name }) {
return <p>Hello, {name}!</p>;
}
const names = ["Ali", "Fatima", "Zara"];
function GreetingsList() {
return (
<>
{names.map((n, i) => (
<Greeting key={i} name={n} />
))}
</>
);
}
✅ Best Practices with Examples
1. Use a unique and stable key (preferably an ID)
✅ React uses keys to identify which items have changed, been added, or removed.
const todos = [
{ id: 101, text: "Learn React" },
{ id: 102, text: "Build a project" },
];
function TodoList() {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
2. Avoid using index as a key unless you have no alternative
⚠️ Using index as a key can lead to rendering issues if items are reordered or removed.
// Not recommended
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
3. Extract list item into its own component
✅ This makes your code cleaner and promotes reusability.
function Task({ task }) {
return <li>{task.name}</li>;
}
function TaskList({ tasks }) {
return (
<ul>
{tasks.map(t => (
<Task key={t.id} task={t} />
))}
</ul>
);
}
4. Avoid dynamic keys like Math.random()
or Date.now()
⚠️ These generate new keys every render, defeating the purpose of React’s key tracking.
5. Use <>
fragments when needed
✅ Useful when rendering multiple siblings without an extra DOM node.
function ProductList({ products }) {
return (
<>
{products.map(p => (
<div key={p.id}>
<h4>{p.name}</h4>
<p>{p.price}</p>
</div>
))}
</>
);
}
🌍 Real-world Scenario
When building a to-do list, each task item should have a unique id
to avoid UI glitches during add/remove/reorder operations.
Conditional Rendering
🧠 Detailed Explanation
In React, conditional rendering means showing different things on the screen depending on some condition — like whether a user is logged in or not.
It's like saying: “If this is true, show this part. Otherwise, show something else.”
You can use different ways to write these conditions:
- ✅ Ternary operator: Show one thing or another based on a true/false check.
- ✅ Logical AND (
&&
): Show something only if the condition is true. - ✅
if
statement: For more complex logic, use this before the return.
This helps you make smart UI — for example, hiding a "Logout" button if the user isn't logged in, or showing a "Welcome" message if they are.
🧠 React only shows what you tell it to — and conditional rendering gives you that power!
💡 Examples
Example 1: Using Ternary Operator
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <p>Welcome back!</p> : <p>Please sign in.</p>}
</div>
);
}
Explanation: If isLoggedIn
is true, it shows “Welcome back!” — otherwise, it shows “Please sign in.”
Example 2: Using Logical AND (&&)
function NewMessageAlert({ hasNewMessages }) {
return (
<div>
<p>Inbox</p>
{hasNewMessages && <p>You have new messages!</p>}
</div>
);
}
Explanation: If hasNewMessages
is true, it displays the alert. If false, it displays nothing.
Example 3: Using if
statement (outside JSX)
function Dashboard({ isAdmin }) {
let content;
if (isAdmin) {
content = <p>Admin Panel</p>;
} else {
content = <p>User Dashboard</p>;
}
return <div>{content}</div>;
}
Explanation: This uses plain JavaScript before returning JSX — great for complex logic.
Example 4: Showing or Hiding Components
function ProductCard({ isAvailable }) {
return (
<div>
<h2>Product Name</h2>
{isAvailable ? <button>Buy Now</button> : <span>Out of Stock</span>}
</div>
);
}
Explanation: The Buy button appears only if the product is available.
Example 5: Nesting Conditions
function UserStatus({ isLoggedIn, hasMessages }) {
return (
<div>
{isLoggedIn ? (
hasMessages ? <p>You have new messages!</p> : <p>No new messages.</p>
) : (
<p>Please log in.</p>
)}
</div>
);
}
Explanation: This shows how you can nest conditions to handle more than one check.
🔁 Alternatives
You can use switch
or external helper functions for more complex UI logic. In some advanced cases, state machines or condition-mapping objects are used.
❓ General Questions & Answers
Q1: What is conditional rendering in React?
A: Conditional rendering means showing different UI elements based on a condition. It works like regular JavaScript if
statements or ternary (condition ? true : false
) expressions but used inside JSX. It helps you control what the user sees depending on app state.
Q2: What's the most common way to render content conditionally?
A: The most common method is using a ternary operator like this:
{isLoggedIn ? <p>Welcome!</p> : <p>Please log in.</p>}
This checks if isLoggedIn
is true and shows the appropriate message.
Q3: What’s the difference between using if
and ?
in JSX?
A: if
statements can’t be used directly inside JSX, but ternary expressions can. You can use if
outside JSX to set a variable, then render that variable.
// ✅ This works
let content;
if (isAdmin) {
content = <AdminPanel />;
} else {
content = <UserPanel />;
}
return <div>{content}</div>;
// ❌ This does NOT work
return (
<div>
if (isAdmin) { <AdminPanel /> }
</div>
);
Q4: When should I use &&
for conditional rendering?
A: Use &&
when you want to render something **only if a condition is true**. Nothing will show if the condition is false.
{hasError && <p>Something went wrong!</p>}
Q5: Can I return null
to hide a component?
A: Yes! In React, if a component returns null
, nothing is rendered. This is useful when you don’t want to show anything under certain conditions.
function Notification({ show }) {
if (!show) return null;
return <div>🔔 You have a new notification!</div>;
}
🛠️ Technical Q&A with Examples
Q1: How do I conditionally render a component in React?
A: Use a ternary operator inside JSX to render different components based on a condition.
function Dashboard({ isAdmin }) {
return (
<div>
{isAdmin ? <AdminPanel /> : <UserPanel />}
</div>
);
}
Explanation: The component shows AdminPanel
if isAdmin
is true, otherwise UserPanel
.
Q2: What happens if I return null
from a component?
A: React will render nothing for that component. This is useful when you want to hide something completely.
function Banner({ show }) {
if (!show) return null;
return <div>🎉 Welcome back!</div>;
}
Q3: How do I render content only when multiple conditions are true?
A: You can use logical AND (&&
) chaining.
{isLoggedIn && hasPermission && <SecretArea />}
Explanation: If both conditions are true, SecretArea
is shown.
Q4: How can I cleanly manage multiple conditions?
A: Abstract logic into variables before the return statement to keep JSX clean.
function Profile({ isLoading, error }) {
let content;
if (isLoading) {
content = <p>Loading...</p>;
} else if (error) {
content = <p>Oops! Something went wrong.</p>;
} else {
content = <p>Profile loaded.</p>;
}
return <div>{content}</div>;
}
Q5: How to conditionally apply JSX attributes or classes?
A: Use ternary or logical operators directly inside props.
<button className={isActive ? "btn active" : "btn"}>
Click
</button>
Explanation: If isActive
is true, the button gets an extra active
class.
✅ Best Practices with Examples
1. Use short-circuiting (&&
) for simple conditions
✅ When you want to render something only if a condition is true, &&
is concise and readable.
{isLoggedIn && <LogoutButton />}
Explanation: If isLoggedIn
is true, it renders the LogoutButton
.
2. Use ternary operators for two options
✅ Use ternary operator to clearly show two different UI outcomes.
{isAdmin ? <AdminDashboard /> : <UserDashboard />}
Explanation: Picks between admin and user views based on a single flag.
3. Avoid deeply nested conditionals in JSX
⚠️ Complex conditions make JSX harder to read. Use variables to hold content logic before returning JSX.
function Message({ status }) {
let content;
if (status === "loading") content = <p>Loading...</p>;
else if (status === "error") content = <p>Error occurred</p>;
else content = <p>Data loaded</p>;
return <div>{content}</div>;
}
4. Return null
to render nothing
✅ Use null
for hidden or optional components.
function WelcomeBanner({ show }) {
if (!show) return null;
return <div>Welcome back!</div>;
}
5. Extract conditional sections into small components
✅ For maintainability, move conditional UI to separate components.
// ❌ Messy
{isPremium ? <h2>Premium User</h2> : <h2>Free User</h2>}
// ✅ Cleaner
function UserLabel({ isPremium }) {
return <h2>{isPremium ? "Premium User" : "Free User"}</h2>;
}
6. Use logical OR (||
) to show default content
✅ Show fallback UI if a value is empty or undefined.
<p>{username || "Guest"}</p>
Explanation: If username
is falsy, it shows "Guest"
.
🌍 Real-world Scenario
In a job portal, conditional rendering helps to show different dashboards for recruiters and job seekers based on their user role.
useState
🧠 Detailed Explanation
useState
is a special function in React that lets your component “remember” something — like a number, text, or toggle value — between renders.
In plain words: it helps your component keep track of values that change when users interact with it.
Example: If you want to build a counter that updates every time a button is clicked, you need a place to store the count. That’s where useState
comes in.
How it works:
const [count, setCount] = useState(0);
- 👉
count
is the current value (starting at0
) - 👉
setCount
is the function that updates the value
When you call setCount(1)
, React re-renders the component with the new value.
This is what makes React apps interactive — they can “remember” what’s changed!
💡 Examples
Example 1: Basic Counter
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
Explanation: This creates a simple counter. Every time the button is clicked, setCount
updates the state, and the new value is shown automatically.
Example 2: Live Input Field
function NameInput() {
const [name, setName] = useState("");
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<p>You typed: {name}</p>
</div>
);
}
Explanation: As the user types into the input, the state is updated in real-time and reflected in the paragraph below.
Example 3: Toggle Visibility
function ToggleText() {
const [visible, setVisible] = useState(true);
return (
<div>
<button onClick={() => setVisible(!visible)}>
Toggle
</button>
{visible && <p>Now you see me!</p>}
</div>
);
}
Explanation: This toggles a message on and off when the button is clicked.
Example 4: Managing Multiple Values
function Form() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
return (
<form>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Login</button>
</form>
);
}
Explanation: You can use multiple useState
hooks to manage different pieces of state in the same component.
🔁 Alternatives or Related Concepts
useReducer()
– for complex state logicuseRef()
– for storing values without triggering re-render
❓ General Questions & Answers
Q1: What is useState
used for in React?
A: useState
is a function provided by React that lets your components “remember” things — like a number, text input, or visibility toggle — between renders. Without it, your components would forget everything every time they re-render.
Q2: What does useState(0)
mean?
A: The number 0
is the initial value for the state. You can start with anything — a string, number, object, or even an array.
const [count, setCount] = useState(0); // count starts at 0
const [name, setName] = useState(""); // name starts as an empty string
Q3: Can I use multiple useState
hooks in one component?
A: Yes, and it’s very common. Each useState
call manages a separate piece of state.
const [name, setName] = useState("");
const [age, setAge] = useState(0);
Q4: Why doesn’t the state update right away?
A: React updates state asynchronously (in the background). That means if you call setCount(5)
, the value of count
won’t change until the next render.
setCount(5);
console.log(count); // still shows the old value!
Q5: What happens when I update state?
A: When you call the updater function like setCount()
, React schedules a re-render of the component, and during the next render, the new state value will be used.
🛠️ Technical Q&A with Examples
Q1: Why does console.log
show the old value after setState()
?
A: Because useState
updates state asynchronously. That means the updated value is only available after the next render.
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // still shows the old value
}
✅ Solution: Use useEffect
to watch the state if you need to act after it changes.
Q2: How do I update state based on the previous value?
A: Use the functional form of setState
to ensure you’re working with the latest value.
setCount(prev => prev + 1);
Q3: Can I store objects or arrays in useState
?
A: Yes! But remember that setting a new object replaces the old one — you need to copy the previous state if you want to keep other values.
// Object
const [user, setUser] = useState({ name: "Ali", age: 25 });
setUser(prev => ({ ...prev, age: 26 }));
// Array
const [items, setItems] = useState([]);
setItems(prev => [...prev, "New item"]);
Q4: What happens if I call setState()
with the same value?
A: React won’t re-render the component if the state hasn’t changed. It’s optimized for performance.
setCount(5); // If count is already 5, React skips the update.
Q5: Can I conditionally use useState
?
A: ❌ No. React Hooks must be called in the same order every time. Never call useState
inside conditions or loops.
// ❌ Incorrect
if (someCondition) {
const [val, setVal] = useState(0); // This breaks rules of hooks!
}
// ✅ Correct
const [val, setVal] = useState(someCondition ? 1 : 0);
✅ Best Practices with Examples
1. Use separate state variables for unrelated data
✅ This makes your code easier to read and update.
const [name, setName] = useState("");
const [age, setAge] = useState(0);
2. Use functional updates if new state depends on old state
✅ Prevents stale closures and guarantees the latest value.
setCount(prev => prev + 1);
3. Use lazy initialization for expensive state setup
✅ Saves performance by only running once on mount.
const [data, setData] = useState(() => heavyComputation());
4. Don’t update state if the value hasn’t changed
✅ React skips re-render if the value is the same, but it’s still a good habit.
if (newValue !== current) {
setValue(newValue);
}
5. Update objects/arrays immutably
✅ Never mutate state directly. Use copies with spread syntax.
// Array
setList(prev => [...prev, "new item"]);
// Object
setUser(prev => ({ ...prev, age: 30 }));
6. Combine multiple states only when they’re logically tied
✅ Use an object when values always change together (e.g., form fields).
const [form, setForm] = useState({ name: "", email: "" });
setForm(prev => ({ ...prev, email: "new@example.com" }));
7. Never call useState()
inside conditions or loops
✅ Hooks must run in the same order every render.
🌍 Real-world Scenario
In a todo list app, you use useState
to track the list of todos, input field values, and toggle filters like "completed only".
useEffect
🧠 Detailed Explanation
useEffect
is a React Hook that lets your component run code after it renders. It's like telling React: "After you're done showing things on the screen, run this function."
You use it when you want to:
- ✅ Fetch data from an API
- ✅ Listen to events like scrolling or resizing
- ✅ Set timers (e.g.,
setInterval
) - ✅ Clean up things (like clearing a timer)
Here's the basic format:
useEffect(() => {
// Your code here (runs after render)
}, []);
The second part (the square brackets []
) is very important. It tells React when to run the effect.
- 📌
[]
→ Run only once (on page load) - 📌
[count]
→ Run whencount
changes - 📌 No brackets → Run after every render
You can also “clean up” your effect — like stopping a timer or removing an event — by returning a function inside the effect:
useEffect(() => {
const timer = setInterval(() => {
console.log("Tick");
}, 1000);
return () => clearInterval(timer); // Cleanup
}, []);
This cleanup runs when the component is removed from the page, or before the effect runs again.
🧠 Summary: useEffect
runs after the screen updates. You use it to fetch data, handle subscriptions, and clean things up when needed.
💡 Examples
Example 1: Run only once when the component loads
import { useEffect } from "react";
function Welcome() {
useEffect(() => {
console.log("Component loaded!");
}, []);
return <h2>Welcome to the page</h2>;
}
Explanation: This effect runs only once — like componentDidMount
— when the component is first shown.
Example 2: Fetching user data
import { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(data => setUsers(data));
}, []);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
Explanation: The effect calls an API and updates the UI once the data is ready.
Example 3: Responding to state changes
import { useEffect, useState } from "react";
function CounterLogger() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Count changed:", count);
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>Add ({count})</button>
);
}
Explanation: This effect runs every time count
changes.
Example 4: Set interval and clean up
import { useEffect, useState } from "react";
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
return () => clearInterval(interval); // Cleanup
}, []);
return <p>Timer: {seconds} seconds</p>;
}
Explanation: The timer starts once and stops when the component unmounts. Cleanup avoids memory leaks.
Example 5: Handle window resize
import { useEffect, useState } from "react";
function WindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return <p>Window width: {width}px</p>;
}
Explanation: Adds and removes a resize listener using useEffect
.
🔁 Alternatives or Related Concepts
componentDidMount
,componentDidUpdate
(in class components)useLayoutEffect
– fires synchronously after all DOM mutations
❓ General Questions & Answers
Q1: What is useEffect
used for?
A:
useEffect
lets you run side effects in your React component. A “side effect” is any action that happens outside the component — like:
- Fetching data from an API
- Setting up or removing event listeners
- Using timers like
setInterval
- Manipulating the DOM directly (rarely needed)
Q2: When does useEffect
run?
A: It depends on the second argument you pass:
[]
→ Runs only once after the first render[count]
→ Runs whencount
changes- Nothing → Runs after every render
useEffect(() => {
console.log("This runs every render");
}); // no second argument
Q3: Can I use async functions inside useEffect
?
A: Not directly. You should define the async function inside the effect and call it:
useEffect(() => {
async function fetchData() {
const res = await fetch("/api/data");
const data = await res.json();
console.log(data);
}
fetchData();
}, []);
Q4: How do I clean up after an effect?
A: You return a function from inside useEffect
. This cleanup runs when the component unmounts or before the effect runs again.
useEffect(() => {
const id = setInterval(() => console.log("tick"), 1000);
return () => clearInterval(id); // cleanup
}, []);
Q5: What’s the difference between useEffect
and just calling a function?
A: Functions inside your component run every time it renders. useEffect
helps you control when the code runs — like only once or only when specific data changes.
🛠️ Technical Q&A with Examples
Q1: Why does my effect run multiple times in development?
A: React 18’s Strict Mode intentionally runs components twice (only in development) to help catch bugs. This includes effects.
✅ Solution: It won’t happen in production, so you can ignore it or disable Strict Mode while testing.
Q2: How do I avoid infinite loops in useEffect
?
A: If you forget the dependency array or include values that change on every render (like functions or objects), your effect might run forever.
// ❌ This causes an infinite loop
useEffect(() => {
setCount(count + 1);
});
✅ Solution: Use a proper dependency array:
useEffect(() => {
setCount(count + 1);
}, [count]); // Controlled update
Q3: How can I fetch data with async/await inside useEffect
?
A: You can’t make the main useEffect
function async. Instead, define an inner function.
useEffect(() => {
const fetchData = async () => {
const res = await fetch("/api");
const json = await res.json();
console.log(json);
};
fetchData();
}, []);
Q4: How do I clean up a subscription or timer?
A: Return a function from useEffect
. React runs this cleanup before the component unmounts or before the effect re-runs.
useEffect(() => {
const interval = setInterval(() => {
console.log("Tick");
}, 1000);
return () => clearInterval(interval);
}, []);
Q5: Can I pass props or state to useEffect
?
A: Yes — include them in the dependency array. This tells React to re-run the effect when those values change.
useEffect(() => {
console.log("User changed:", user.name);
}, [user.name]);
⚠️ Tip: Avoid using full objects or functions directly — use their dependencies instead, or memoize them with useCallback
or useMemo
.
✅ Best Practices with Examples
1. Always use the dependency array
✅ It tells React when to re-run your effect.
useEffect(() => {
console.log("This runs once");
}, []); // Empty array = run only once
2. Don’t make the main useEffect function async
✅ Define an async function inside, then call it.
useEffect(() => {
const fetchData = async () => {
const res = await fetch("/api");
const data = await res.json();
console.log(data);
};
fetchData();
}, []);
3. Return a cleanup function when needed
✅ Use cleanup to avoid memory leaks for intervals, subscriptions, or listeners.
useEffect(() => {
const interval = setInterval(() => {
console.log("Tick");
}, 1000);
return () => clearInterval(interval);
}, []);
4. Don’t over-declare dependencies
✅ Only include what's necessary. For stable functions, use useCallback
.
useEffect(() => {
doSomething(); // avoid re-running unnecessarily
}, [doSomething]); // useCallback helps avoid re-renders
5. Use multiple useEffect calls for unrelated logic
✅ Don’t lump everything in one giant effect.
useEffect(() => {
// fetch user
}, []);
useEffect(() => {
// listen to resize
}, []);
6. Cancel async requests on cleanup
✅ Prevent setting state on an unmounted component.
useEffect(() => {
let isMounted = true;
async function fetchData() {
const res = await fetch("/api");
const data = await res.json();
if (isMounted) {
setData(data);
}
}
fetchData();
return () => {
isMounted = false;
};
}, []);
7. Add comments if the effect logic is long or tricky
✅ Helps other developers (and future you) understand it faster.
🌍 Real-World Scenario
In a chat app, use useEffect
to connect to a WebSocket when the component mounts and clean it up when the component unmounts.
useContext
🧠 Detailed Explanation
Imagine you want to share some data like a theme color or user name across many components in your app. Normally, you'd pass it down through props — but that can get messy if you have to go through many layers. That’s where useContext
comes in!
useContext
is a React Hook that lets you get data from a "context" — like a shared global container — without passing props manually.
Here's how it works in three simple steps:
- 1. Create a Context – This is like making a box to hold your data.
- 2. Provide the Context – Wrap your component tree and give it a value.
- 3. Use
useContext()
– In any child component, you can now access that value directly!
It's great for things like:
- 🌗 Dark mode / Light mode (themes)
- 👤 User authentication
- 🌐 Language settings
- 🛒 Shopping cart data
🧠 Summary: useContext
helps you avoid prop-drilling and makes sharing data between components easier and cleaner.
💡 Examples
🧪 Example 1: Creating and using a Theme Context
Step 1: Create a Context
import React from 'react';
export const ThemeContext = React.createContext('light'); // default: 'light'
Step 2: Provide the Context value
import React, { useState } from 'react';
import { ThemeContext } from './ThemeContext';
import ChildComponent from './ChildComponent';
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<ChildComponent />
</ThemeContext.Provider>
);
}
Step 3: Use useContext()
to read the value
import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
function ChildComponent() {
const theme = useContext(ThemeContext);
return <div className={`box ${theme}`}>Current Theme: {theme}</div>;
}
Explanation: The ChildComponent
uses the current value of theme
from the context and updates the style or content accordingly — without needing it passed via props.
🧪 Example 2: Sharing and updating user data across components
const UserContext = React.createContext();
function App() {
const [user, setUser] = useState({ name: 'Saad', loggedIn: true });
return (
<UserContext.Provider value={{ user, setUser }}>
<Navbar />
</UserContext.Provider>
);
}
function Navbar() {
const { user } = useContext(UserContext);
return <div>Welcome, {user.name}!</div>;
}
Explanation: The Navbar
can access and display the user info, even though it’s a deeply nested component. No need to pass user
through multiple props!
🔁 Alternatives
props
— traditional way, but not scalable for deeply nested componentsRedux
orZustand
— for complex global state
❓ General Questions & Answers
Q1: What is useContext
in React?
A: useContext
is a React Hook that lets you read values from a React Context. It helps you avoid passing props manually through many levels of components.
Real-life example: Instead of passing the theme
value from App → Header → Nav → Button, you can directly access it anywhere using useContext(ThemeContext)
.
Q2: When should I use useContext
?
A: Use it when multiple components need access to the same data — like current user info, authentication status, app theme, or language settings.
- ✅ Great for global state like user, cart, or theme
- ✅ Better than prop-drilling through many components
Q3: Can I update context values with useContext
?
A: Yes — but only if the context provider gives both the value and the setter function.
<UserContext.Provider value={{ user, setUser }}>
...
</UserContext.Provider>
const { user, setUser } = useContext(UserContext);
setUser({ name: "Ali" });
Q4: Does useContext
work across files?
A: Yes! You can create a context in one file (like ThemeContext.js
), provide it in App.js
, and use it anywhere in the app.
Q5: What happens if there’s no matching Provider
?
A: React will return the default value passed to createContext(defaultValue)
. But usually, you should wrap your app in the corresponding Provider.
const ThemeContext = React.createContext("light");
const theme = useContext(ThemeContext); // returns "light" if no Provider
🛠️ Technical Q&A with Examples
Q1: How do I update context data using useContext
?
A: Provide a function (like setUser
) along with the value in your context provider. Then, call that function in any child component.
// In your App
const [user, setUser] = useState("Saad");
<UserContext.Provider value={{ user, setUser }}>
<Profile />
</UserContext.Provider>
// In child
const { user, setUser } = useContext(UserContext);
setUser("Ali");
Q2: What if I need multiple pieces of data in context?
A: Group related data and functions into a single object and pass it as the value
.
const AuthContext = React.createContext();
<AuthContext.Provider value={{ user, token, login, logout }}>
...
</AuthContext.Provider>
const { login, user } = useContext(AuthContext);
Q3: Can I use multiple contexts in the same component?
A: Yes, just call useContext
multiple times — one for each context.
const theme = useContext(ThemeContext);
const auth = useContext(AuthContext);
Q4: Why is my context not updating?
A: Common reasons:
- You're mutating state instead of replacing it
- The component is not inside the Provider
- You forgot to re-render by calling a setter function
// ❌ Wrong
user.name = "Ali"; // won't trigger re-render
// ✅ Correct
setUser({ ...user, name: "Ali" });
Q5: Should I memoize context values?
A: Yes, if the value object is complex or you notice unnecessary re-renders.
const value = useMemo(() => ({ user, setUser }), [user]);
<UserContext.Provider value={value}>
...
</UserContext.Provider>
✅ Best Practices with Examples
1. Provide Context at a high level (usually in App.js
)
✅ This ensures all components can access the context.
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<MainApp />
</ThemeContext.Provider>
);
}
2. Use useContext
instead of prop drilling
✅ This keeps your components clean and easy to manage.
// ❌ Not ideal: Passing props through many levels
<App user={user}>
<Layout user={user}>
<Navbar user={user} />
</Layout>
</App>
// ✅ Better with useContext
const { user } = useContext(UserContext);
3. Bundle multiple values in context (state + actions)
✅ This allows more flexibility for consuming components.
const AuthContext = createContext();
<AuthContext.Provider value={{ user, login, logout }}>
...
</AuthContext.Provider>
const { user, login } = useContext(AuthContext);
4. Memoize complex values with useMemo
✅ Prevents unnecessary re-renders of consuming components.
const value = useMemo(() => ({ user, setUser }), [user]);
<UserContext.Provider value={value}>
...
</UserContext.Provider>
5. Use custom hooks to simplify context access
✅ Keeps your components cleaner and reduces repetitive code.
// user-context.js
export const UserContext = createContext();
export const useUser = () => useContext(UserContext);
// usage
const { user } = useUser();
6. Be cautious with multiple nested providers
✅ Extract and name each one clearly to maintain readability.
<ThemeProvider>
<AuthProvider>
<LangProvider>
<App />
</LangProvider>
</AuthProvider>
</ThemeProvider>
🌍 Real-world Scenario
In a multi-language website, LanguageContext
is used to provide the current language to every component — so buttons, labels, and messages auto-translate based on the selected language.
useRef
🧠 Detailed Explanation
useRef
is a special hook in React that lets you create a reference to a value or a DOM element.
Think of it like a box that can hold a value — and even when your component re-renders, that box stays the same.
There are 2 main uses for useRef
:
- 1. Accessing DOM elements directly: like focusing an input field, scrolling to a section, or reading a value from the page.
- 2. Storing values that don’t trigger re-renders: such as timers, previous values, or counters that change without updating the UI.
For example, if you want to focus an input when a button is clicked, you can use useRef
to grab that input and run .focus()
on it — just like in regular JavaScript.
Important: When you update ref.current
, React does not re-render the component.
So, if you just need to store or interact with something without changing the UI — useRef
is perfect for that!
💡 Examples
Example 1: Focusing an input when a button is clicked
import React, { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus(); // Focus the input directly
}
return (
<>
<input ref={inputRef} placeholder="Type here..." />
<button onClick={handleFocus}>Focus Input</button>
</>
);
}
Explanation: We create a ref using useRef
and attach it to the input. When the button is clicked, it calls .focus()
on the input field.
Example 2: Keeping track of the previous value
import React, { useState, useRef, useEffect } from 'react';
function PreviousValue() {
const [count, setCount] = useState(0);
const prevCount = useRef();
useEffect(() => {
prevCount.current = count;
}, [count]);
return (
<>
<p>Current: {count}</p>
<p>Previous: {prevCount.current}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
);
}
Explanation: After each render, useEffect
updates the ref to store the current value. We can still access the previous value on the next render without causing re-renders.
Example 3: Storing a timer ID with useRef
import React, { useRef } from 'react';
function TimerComponent() {
const timerRef = useRef(null);
const startTimer = () => {
timerRef.current = setTimeout(() => {
alert('Timer finished!');
}, 3000);
};
const cancelTimer = () => {
clearTimeout(timerRef.current);
alert('Timer cancelled');
};
return (
<>
<button onClick={startTimer}>Start Timer</button>
<button onClick={cancelTimer}>Cancel Timer</button>
</>
);
}
Explanation: You can store timeout/interval IDs in useRef
to clear them later without needing state.
Example 4: Scroll into view
import React, { useRef } from 'react';
function ScrollToSection() {
const sectionRef = useRef();
const scrollTo = () => {
sectionRef.current.scrollIntoView({ behavior: 'smooth' });
};
return (
<>
<button onClick={scrollTo}>Scroll to Section</button>
<div style={{ marginTop: '1000px' }} ref={sectionRef}>
<h2>Target Section</h2>
</div>
</>
);
}
Explanation: Clicking the button scrolls the page smoothly to the referenced section using the DOM's scrollIntoView()
method.
🔁 Alternatives
useState
– for values that should trigger UI updatesuseCallback
– for functions that don’t change every render
❓ General Questions & Answers
Q1: What is useRef
used for?
A: useRef
is mainly used for two things:
- Accessing or modifying DOM elements (like focusing an input or scrolling to a section)
- Storing a value that stays the same between renders — without causing re-renders
It’s like a hidden storage box that React ignores during rendering.
Q2: What’s the difference between useRef
and useState
?
A: The key difference is:
useState
causes a re-render when the value changesuseRef
does not cause a re-render — it’s good for temporary or hidden values
Q3: Can I use useRef
to track values across renders?
A: Yes! You can use it to store the previous value of something or keep a counter that doesn't affect UI rendering.
Example:
const previousValue = useRef();
useEffect(() => {
previousValue.current = currentValue;
}, [currentValue]);
Q4: What does .current
mean in useRef
?
A: The .current
property holds the value inside the ref. You can read from or write to it.
const inputRef = useRef(null);
inputRef.current // refers to the input DOM node
Q5: Is useRef
only for DOM access?
A: No — it's commonly used for DOM access, but it's also great for:
- Timers or intervals
- Storing previous state
- Scroll positions
- Any value you want to persist without re-rendering
🛠️ Technical Q&A with Examples
Q1: How do I access a DOM element using useRef
?
A: Attach ref
to the DOM element and call its methods via ref.current
.
function AutoFocusInput() {
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="Focus me on load" />;
}
Explanation: inputRef.current
points to the actual DOM input element.
Q2: How can I store a previous value using useRef
?
A: Update the ref.current
after rendering using useEffect
.
function PreviousCounter() {
const [count, setCount] = useState(0);
const prevCount = useRef(0);
useEffect(() => {
prevCount.current = count;
}, [count]);
return (
<>
<p>Current: {count}</p>
<p>Previous: {prevCount.current}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</>
);
}
Q3: Can I use useRef
to hold a timer ID?
A: Yes — and it’s actually the best way to persist a timer without triggering re-renders.
function Timer() {
const timerRef = useRef(null);
function startTimer() {
timerRef.current = setTimeout(() => {
alert("Timer finished");
}, 3000);
}
function cancelTimer() {
clearTimeout(timerRef.current);
}
return (
<>
<button onClick={startTimer}>Start</button>
<button onClick={cancelTimer}>Cancel</button>
</>
);
}
Q4: Why doesn’t updating ref.current
cause a re-render?
A: Because useRef
is designed to persist values across renders silently. It’s like a regular variable outside of React's render lifecycle.
const counter = useRef(0);
function handleClick() {
counter.current++;
console.log(counter.current); // ✅ value updates but UI doesn’t re-render
}
Q5: What happens if I access ref.current
before the component is mounted?
A: The ref.current
value will be undefined
until the DOM node is attached. Use useEffect
to interact safely after mount.
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
✅ Best Practices with Examples
1. Use useRef
for direct DOM manipulation
✅ When you need to access or interact with a DOM node (like focusing, scrolling, or measuring), useRef
is the best tool.
const inputRef = useRef(null);
function focusInput() {
inputRef.current.focus(); // direct DOM access
}
2. Store non-UI values without triggering re-renders
✅ Use useRef
for counters, previous values, timer IDs, or flags that don’t affect the UI.
const renderCount = useRef(0);
useEffect(() => {
renderCount.current++;
console.log("Render count:", renderCount.current);
});
3. Don’t use useRef
for values that should trigger UI updates
❌ useRef
won’t re-render the UI if you update it — so use useState
when the UI should respond to a change.
// ❌ Won’t re-render:
const nameRef = useRef("John");
nameRef.current = "Jane"; // UI doesn't update
// ✅ Correct way:
const [name, setName] = useState("John");
setName("Jane"); // UI updates
4. Update useRef
inside useEffect
for previous values
✅ This lets you track past state values without re-rendering.
const prevValue = useRef();
useEffect(() => {
prevValue.current = currentValue;
}, [currentValue]);
5. Store timeout/interval IDs in useRef
✅ Helps manage background timers without needing state or cleanup headaches.
const timer = useRef();
function start() {
timer.current = setTimeout(() => console.log("Time up!"), 3000);
}
function cancel() {
clearTimeout(timer.current);
}
6. Use clear, specific names for your refs
✅ Instead of just ref
, name it inputRef
, timerRef
, etc., so the code is self-explanatory.
🌍 Real-World Scenarios
-
1. Auto-focusing an input field on form load
When a login or search form loads, useuseRef
to automatically focus the first input field to improve user experience. -
2. Storing timer IDs for canceling timeouts or intervals
Perfect for notifications, countdowns, or debouncing where you need to clear a timer. -
3. Keeping track of previous values (e.g. for comparisons)
You can track the previous prop/state value to show history, log changes, or conditionally render components. -
4. Scrolling to a section of the page
For features like “scroll to top” or jumping to a product section —useRef
can reference DOM elements and scroll smoothly. -
5. Managing focus when using modals or popups
When opening a modal, you can useuseRef
to focus on the close button or a form input for accessibility. -
6. Persisting values across renders without triggering UI updates
Useful for tracking how many times a button was clicked, storing audio/video playback status, or managing form "dirty" states. -
7. Storing WebSocket or API instance
When you connect to a WebSocket or external service and don’t want to reinitialize it every render — useRef keeps the instance alive without re-renders.
useReducer
🧠 Detailed Explanation
useReducer
is a React hook that helps you manage complex or related pieces of state inside a component.
It’s like a more organized version of useState
— instead of calling multiple setState
functions, you use a single function called reducer
that decides how to update the state based on an action.
Think of it like this:
🔁 You say “Do this”
→ 🧠 The reducer function decides → 🎯 The new state is set.
You use dispatch()
to send an action, and the reducer figures out how to handle that action.
Example (simple counter):
- Action:
{ type: "increment" }
- Reducer: decides to add 1 to the count
- State updates to reflect the new count
✅ It’s especially helpful when:
- You have multiple values in your state that change together
- You want to clearly organize how your state changes
- You’re building something like a form, shopping cart, or multi-step logic
💡 Examples
Example 1: Simple Counter
// 1. Reducer function
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
}
// 2. Using useReducer
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
}
Explanation: You dispatch actions like "increment"
and the reducer decides how to change the state.
Example 2: Toggle Button
function reducer(state, action) {
switch (action.type) {
case "toggle":
return { isOn: !state.isOn };
default:
return state;
}
}
function Toggle() {
const [state, dispatch] = useReducer(reducer, { isOn: false });
return (
<button onClick={() => dispatch({ type: "toggle" })}>
{state.isOn ? "ON" : "OFF"}
</button>
);
}
Explanation: The button switches between ON and OFF each time it is clicked.
Example 3: Todo List with Add and Remove
function reducer(state, action) {
switch (action.type) {
case "add":
return [...state, { text: action.text, id: Date.now() }];
case "remove":
return state.filter((todo) => todo.id !== action.id);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(reducer, []);
const [text, setText] = useState("");
function handleAdd() {
dispatch({ type: "add", text });
setText("");
}
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={handleAdd}>Add Todo</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
<button onClick={() => dispatch({ type: "remove", id: todo.id })}>X</button>
</li>
))}
</ul>
</div>
);
}
Explanation: Todos are added or removed using dispatch()
. Each action is handled by the reducer.
🔁 Alternative Methods
You can use useState
for simpler logic or Redux
for global state across multiple components.
❓ General Questions & Answers
Q1: When should I use useReducer
instead of useState
?
A: Use useReducer
when you have complex state logic — like multiple values that depend on each other, or when your state updates depend on the previous state. It's especially helpful for forms, toggles, counters, or any logic with "actions".
Q2: What does the dispatch
function do?
A: The dispatch
function is used to send an action to the reducer. The reducer then looks at the action and returns the new state based on that. Think of it like saying, “Hey reducer, do this!”
Q3: Is useReducer
similar to Redux?
A: Yes, very similar. Both use actions and reducers. But useReducer
is built into React and works only in the component where you use it. Redux works globally across the entire app.
Q4: Can I manage multiple state values with one useReducer
?
A: Yes! That’s one of its strengths. Instead of using multiple useState
calls, you can group related state values in one object and manage them with a single reducer function.
Q5: Do I need to memorize action types?
A: Not really — you just define them yourself (e.g., "increment"
, "reset"
). It's a good practice to keep them consistent and readable, like constants.
🛠️ Technical Q&A with Examples
Q1: How do I pass data in an action?
A: You include additional fields in the action object. Example:
dispatch({ type: "add", text: "Buy Milk" });
The reducer then uses action.text
to update the state.
Q2: Can I use multiple useReducer
hooks in one component?
A: Yes. Just like useState
, you can use multiple reducers to manage separate parts of the state.
const [userState, dispatchUser] = useReducer(userReducer, {});
const [formState, dispatchForm] = useReducer(formReducer, {});
Q3: Can I use side effects inside a reducer?
A: No. Reducers must be pure functions — they should only calculate and return new state. For side effects (like API calls), use useEffect
.
Q4: What if I need to reset the entire state?
A: Handle a "reset"
action in the reducer to return the initial state.
case "reset":
return initialState;
Q5: How can I handle different types of updates?
A: Use a switch
statement in the reducer and define different logic for each action.type
:
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return { count: 0 };
default:
return state;
}
}
✅ Best Practices with Examples
1. Use constants for action types (optional but helpful)
Using constants reduces typos and makes your reducer easier to manage.
// Define constants
const INCREMENT = "increment";
const RESET = "reset";
// Use them
dispatch({ type: INCREMENT });
2. Reducer functions should be pure
Don’t fetch data or call APIs inside the reducer. Just calculate and return the new state.
// ✅ GOOD
function reducer(state, action) {
return { ...state, count: state.count + 1 };
}
// ❌ BAD: This causes side effects inside reducer
function reducer(state, action) {
fetch('/api'); // don't do this here
return state;
}
3. Use descriptive action names
Instead of vague names like “doSomething”
, use meaningful names like “addItem”
or “removeTodo”
.
4. Group related state together
If your component has multiple related values, useReducer helps you keep them in one place:
const initialState = {
count: 0,
step: 1,
};
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, count: state.count + state.step };
case "setStep":
return { ...state, step: action.value };
default:
return state;
}
}
5. Combine useReducer + useContext to create simple global state
This is useful when you want to share state between multiple components without installing Redux.
// context.js
export const CounterContext = createContext();
export function CounterProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
}
🌍 Real-World Examples
- 1. Shopping Cart Management
Add, remove, or update product quantities using a reducer to manage cart items and totals. - 2. Multi-Step Form Navigation
Use a reducer to handle switching between form steps, validating fields, and updating form state. - 3. Toggle UI Elements
Toggle dark/light theme, show/hide sidebars, or modals using reducer actions liketoggleTheme
ortoggleModal
. - 4. Authentication State
Handle user login/logout withlogin
andlogout
actions, storing user data or tokens in state. - 5. Notifications System
Add or remove alerts/notifications in a global state that is updated with actions likeaddNotification
ordismissNotification
. - 6. Task Management App (To-do List)
Add, edit, toggle, or delete tasks using a reducer to maintain a clean list state with action types likeaddTask
,toggleTask
,deleteTask
. - 7. Form Error & Success Handling
Manage complex form logic like setting validation errors, showing success messages, and resetting inputs with reducer-based state control.
🚀 Best Implementation: Todo App with useReducer
This implementation helps you understand how to manage complex state in a scalable way using useReducer
.
🔧 Step 1: Define the Reducer
function todoReducer(state, action) {
switch (action.type) {
case "ADD":
return [...state, { id: Date.now(), text: action.payload, completed: false }];
case "TOGGLE":
return state.map(todo =>
todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
);
case "REMOVE":
return state.filter(todo => todo.id !== action.payload);
case "RESET":
return [];
default:
return state;
}
}
Explanation: The reducer handles different actions like adding, toggling, removing, or resetting todos.
🔧 Step 2: Set Up the Component
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, []);
const [text, setText] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (!text.trim()) return;
dispatch({ type: "ADD", payload: text });
setText("");
};
Explanation: You manage the input using useState
and handle changes with a form submit handler.
🧱 Full Component with Actions
return (
<div>
<h2>Todo List</h2>
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a task"
/>
<button type="submit">Add</button>
<button type="button" onClick={() => dispatch({ type: "RESET" })}>Clear All</button>
</form>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
style={{ textDecoration: todo.completed ? "line-through" : "none", cursor: "pointer" }}
onClick={() => dispatch({ type: "TOGGLE", payload: todo.id })}
>
{todo.text}
</span>
<button onClick={() => dispatch({ type: "REMOVE", payload: todo.id })}>❌</button>
</li>
))}
</ul>
</div>
);
✅ Key Points to Learn
- State is managed in a structured way with reducer logic
dispatch()
sends clean actions to trigger updates- Each UI interaction is mapped to a separate, readable action
- Code is clean, scalable, and reusable
This is one of the most recommended real-world patterns when building apps that handle dynamic lists, state transitions, or workflows in React.
useMemo
🧠 Detailed Explanation
useMemo
is a special React Hook that helps your app run faster by remembering the result of a function — **so it doesn't run again unless it needs to**.
Imagine you have a slow or expensive calculation. Normally, React will re-run it every time your component re-renders. But with useMemo
, React will **"remember"** the result from last time — and only re-calculate it if something changes.
Example analogy: It’s like saving a math result in your notebook so you don’t have to redo the calculation every time you need it.
- ✅ Only re-runs the function when inputs (dependencies) change
- ✅ Great for sorting, filtering, and derived values
- ❌ Don’t use it everywhere — only when performance really matters
Think of it like this:
"If the input is the same, just reuse the answer."
💡 Examples
🧪 Example 1: Avoiding expensive calculation on every render
import { useMemo } from "react";
function ExpensiveComponent({ number }) {
const double = useMemo(() => {
console.log("Running heavy calculation...");
let result = 0;
for (let i = 0; i < 1e8; i++) {
result += number * 2;
}
return result;
}, [number]);
return <div>Double: {double}</div>;
}
Explanation: The expensive calculation only runs when number
changes. Otherwise, React remembers the last result and skips the work.
🧪 Example 2: Memoizing a sorted list
function SortedList({ items }) {
const sorted = useMemo(() => {
return [...items].sort((a, b) => a.localeCompare(b));
}, [items]);
return (
<ul>
{sorted.map(item => (
<li key={item}>{item}</li>
))}
</ul>
);
}
Explanation: Sorting is done only when items
change — not every time the component re-renders.
🧪 Example 3: Preventing recalculation on unrelated re-renders
function UserScore({ user }) {
const score = useMemo(() => {
return user.points * 2 + 100;
}, [user.points]);
return <p>Score: {score}</p>;
}
Explanation: Even if other props change, the score is only recalculated if user.points
changes.
🔁 Alternatives
Instead of useMemo
, you can precompute values outside the component for static values. But useMemo
is better when values depend on props/state.
❓ General Questions & Answers
Q1: What is useMemo
used for?
A: useMemo
is used to remember the result of a function between renders. This helps avoid repeating expensive calculations unless the inputs (dependencies) actually change. It improves performance by preventing unnecessary work.
Q2: What’s the difference between useMemo
and useEffect
?
A:
useMemo
returns a **value** that you can use inside your JSX (like a computed result).useEffect
runs a **side effect** (like fetching data or updating the DOM) after the render.
useMemo
when you want to "remember" a value, and use useEffect
when you want to "do something."
Q3: When should I NOT use useMemo
?
A: Avoid useMemo
if:
- The calculation is fast and simple.
- You don’t need to reuse the result.
- It adds confusion instead of clarity.
useMemo
when your app slows down due to heavy calculations.
Q4: Does useMemo
always make performance better?
A: Not always. Sometimes, adding useMemo
can make things more complex without real benefit. It only helps when the cost of recalculating something is high. Otherwise, it may even hurt performance by doing unnecessary dependency checks.
Q5: Can I use useMemo
to avoid re-rendering a component?
A: Not directly. useMemo
helps avoid re-running a function. To prevent an entire component from re-rendering, you might want to use React.memo
. But you can combine both:
const MyComponent = React.memo(function(props) {
const memoizedValue = useMemo(() => ..., [dependencies]);
return <div>{memoizedValue}</div>;
});
🛠️ Technical Q&A with Examples
Q1: How do I use useMemo
to memoize a computed value like filtered data?
A: Use useMemo
to compute once and reuse unless the data changes.
const filteredItems = useMemo(() => {
return items.filter(item => item.active);
}, [items]);
This ensures filtering only happens when items
changes, not on every render.
Q2: Can I use useMemo
for sorting?
A: Yes! Sorting is an ideal use case, especially for large datasets.
const sorted = useMemo(() => {
return [...data].sort((a, b) => a.name.localeCompare(b.name));
}, [data]);
Sorting happens only when data
changes — not every render.
Q3: Why does my useMemo
still run every render?
A: Most likely, your dependency array is incorrect or you're passing in a new object or function each time.
const result = useMemo(() => doSomething(config), [config]); // ❌ config is a new object each time
Fix: Memoize the object too, or break it down into primitive values.
Q4: Can I use useMemo
to cache API responses?
A: No — useMemo
does not persist between renders or fetch fresh data. Use libraries like SWR or React Query for data caching. useMemo
is only for calculations within the same render lifecycle.
Q5: Can useMemo
return JSX?
A: Yes! You can memoize entire UI blocks.
const memoizedContent = useMemo(() => {
return <div>Expensive UI Block</div>;
}, [someProp]);
This is useful for preventing re-render of UI when it doesn't depend on changing props/state.
✅ Best Practices with Examples
1. Use useMemo
only when needed
Don’t use useMemo
everywhere “just in case.” It’s meant for performance optimization when calculations are expensive.
// ✅ Good
const sortedList = useMemo(() => {
return [...items].sort();
}, [items]);
// ❌ Avoid (simple values don't need memoization)
const doubled = useMemo(() => value * 2, [value]);
2. Always provide a dependency array
If you don’t provide the right dependencies, React won’t know when to re-run your function.
// ✅ Correct
const total = useMemo(() => a + b, [a, b]);
// ❌ Will never recalculate even if a or b changes
const total = useMemo(() => a + b, []);
3. Avoid memoizing functions — use useCallback
for that
useMemo
is for values; useCallback
is for functions.
// ✅ For memoizing values
const result = useMemo(() => calculate(data), [data]);
// ✅ For memoizing functions
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
4. Use useMemo
for derived state
Sometimes a piece of state can be calculated from another. Instead of storing it separately, memoize it.
const totalItems = useMemo(() => cart.reduce((acc, item) => acc + item.qty, 0), [cart]);
5. Wrap complex or expensive calculations
If your component feels slow, check if you're recalculating something big every render.
const filtered = useMemo(() => {
return data.filter(item => item.active);
}, [data]);
6. Be careful with objects or arrays as dependencies
If the object is re-created each render, useMemo
will re-run even if contents are the same.
// ❌ Will re-run every time
const result = useMemo(() => calc(obj), [obj]);
// ✅ Use primitives or memoize the object too
const memoObj = useMemo(() => obj, [obj.key1, obj.key2]);
const result = useMemo(() => calc(memoObj), [memoObj]);
🌍 Real-World Examples
-
1. Filtering products in an e-commerce store
useMemo
helps avoid re-filtering the product list every time the user types or clicks around.const filteredProducts = useMemo(() => { return products.filter(p => p.category === selectedCategory); }, [products, selectedCategory]);
-
2. Sorting a list of users in an admin dashboard
Avoid sorting again unless the user list changes.const sortedUsers = useMemo(() => { return [...users].sort((a, b) => a.name.localeCompare(b.name)); }, [users]);
-
3. Generating a color palette from an uploaded image
An expensive color extraction function should only run when the image changes.const palette = useMemo(() => extractColors(image), [image]);
-
4. Calculating tax in an invoice app
The tax calculation should be memoized so it doesn't run every keystroke.const totalTax = useMemo(() => subtotal * 0.15, [subtotal]);
-
5. Displaying a paginated leaderboard
If users change page but the data doesn’t, memoize the current page’s data.const currentPageData = useMemo(() => { const start = (page - 1) * perPage; return leaderboard.slice(start, start + perPage); }, [leaderboard, page]);
-
6. Filtering chat messages in a messaging app
Filter unread messages only when messages change.const unreadMessages = useMemo(() => { return messages.filter(msg => !msg.read); }, [messages]);
-
7. Rendering expensive chart data
Don’t recalculate chart points unless the source data changes.const chartPoints = useMemo(() => calculatePoints(dataset), [dataset]);
🔁 useCallback
🧠 Detailed Explanation
useCallback
is a React Hook that helps you keep the **same function** between renders — unless something important changes.
Normally in React, when your component re-renders, any functions inside it are recreated. This is usually fine, but if you pass those functions to child components (especially ones wrapped in React.memo
), it can cause unnecessary re-renders.
useCallback
fixes this by giving you a version of your function that doesn’t change unless you tell it to.
- ✅ Helps you improve performance by avoiding unnecessary renders
- ✅ Useful when passing functions to child components or using inside
useEffect
- 🛠️ Looks like this:
const fn = useCallback(() => {}, [dependency])
You don’t always need it — only when you want to **prevent re-creating functions** every time your component updates.
💡 Examples
✅ Example 1: Basic counter using useCallback
import { useState, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
Explanation: The increment
function stays the same across renders. Useful if you pass it to a child component.
✅ Example 2: Avoid re-rendering a child component
const Child = React.memo(({ onClick }) => {
console.log("Child rendered");
return <button onClick={onClick}>Click Me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
alert("Button clicked!");
}, []);
return (
<>
<Child onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>Update Parent</button>
</>
);
}
Explanation: Without useCallback
, Child
would re-render even if its props didn’t change. Here, it skips re-render because the function is memoized.
✅ Example 3: Using useCallback in event handling with dependencies
function Greeting({ name }) {
const showAlert = useCallback(() => {
alert(`Hello, ${name}!`);
}, [name]);
return <button onClick={showAlert}>Greet</button>;
}
Explanation: The showAlert
function is updated only when name
changes.
🔁 Alternatives
Instead of useCallback
, sometimes simply defining the function outside the component or inside a useEffect
may suffice. For simple functions, you might not need useCallback
at all.
❓ General Questions & Answers
Q1: What is the main purpose of useCallback
?
A: The main purpose of useCallback
is to prevent functions from being re-created every time a component renders. This is useful for optimizing performance, especially when you pass functions to child components that are memoized (like React.memo
).
Q2: When should I actually use useCallback
?
A: You should use it when:
- You're passing a function to a memoized child component.
- You want to keep a stable reference to a function between renders.
- You’re using that function in a dependency array (like in
useEffect
).
Q3: What’s the difference between useCallback
and useMemo
?
A:
useCallback
is for memoizing functions.useMemo
is for memoizing computed values (like filtered data or calculations).
useCallback
as a shortcut to avoid unnecessary function creation.
Q4: Is useCallback
always better than using a normal function?
A: No. You should use it only when there’s a performance issue due to re-created functions. React is fast by default — adding useCallback
everywhere can make your code harder to read and debug.
Q5: Can I use useCallback
without a dependency array?
A: Technically yes, but it won't work properly. Always provide the correct dependency array — this tells React when to re-create the function. Example:
const fn = useCallback(() => {
console.log(value);
}, [value]);
🛠️ Technical Q&A with Examples
Q1: Why does my child component re-render even with useCallback?
A: This usually happens if the useCallback
function has changing dependencies or is missing dependencies.
const handleClick = useCallback(() => {
console.log(count);
}, []); // ❌ count is used, but not in the dependency array
✅ Fix: Add count
to the dependency array:
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
Q2: How to pass parameters using useCallback?
A: Wrap your function in another arrow function when using parameters.
const handleGreet = useCallback((name) => {
alert(`Hello, ${name}`);
}, []);
<button onClick={() => handleGreet('Aisha')}>Greet</button>
Explanation: This avoids calling the function directly on render.
Q3: Can I use useCallback with API calls?
A: Yes, it’s useful when you pass the API function to another component or use it in useEffect
.
const fetchData = useCallback(async () => {
const res = await fetch('/api/data');
const json = await res.json();
setData(json);
}, []);
Q4: How does useCallback help in large lists?
A: When rendering many items (like in a list), useCallback
avoids re-creating event handlers passed to each item. This reduces re-renders and improves performance.
const handleItemClick = useCallback((id) => {
console.log("Clicked item", id);
}, []);
Q5: How do I debug unnecessary re-renders even with useCallback?
A: Use tools like why-did-you-render
to detect why React is re-rendering. Sometimes memoization is broken due to unstable props (like objects or arrays).
// This object changes every render:
const filter = { search: query }; // ❌
const filter = useMemo(() => ({ search: query }), [query]); // ✅
✅ Best Practices with Examples
1. Don’t overuse useCallback
✅ Use it only when necessary — like when passing functions to React.memo
or inside useEffect
.
function MyComponent({ onAction }) {
// No need to memoize unless performance is impacted
const handleClick = () => {
onAction();
};
return <button onClick={handleClick}>Click</button>;
}
2. Always include all dependencies
✅ Your function should list all the variables it uses inside its body.
const fetchData = useCallback(() => {
getUser(id);
}, [id]); // ✅ include 'id'
3. Combine useCallback
with React.memo
✅ This prevents child components from re-rendering unless props truly change.
const Child = React.memo(({ onClick }) => {
return <button onClick={onClick}>Child</button>;
});
function Parent() {
const handleClick = useCallback(() => {
alert("Clicked!");
}, []);
return <Child onClick={handleClick} />;
}
4. Use it when functions are passed to many child elements
✅ Especially useful in lists, tables, or forms with multiple handlers.
const handleChange = useCallback((id) => {
console.log("Changed:", id);
}, []);
5. Avoid creating inline functions in render if they cause re-renders
✅ Extract functions and memoize if they’re affecting child re-renders.
// ❌ Bad
<button onClick={() => doSomething()}>
// ✅ Good
const handleClick = useCallback(() => doSomething(), []);
<button onClick={handleClick}>
🌍 Real-World Examples of useCallback
-
1. Preventing unnecessary child re-renders:
When passing a function to a memoized child component (e.g.,React.memo
),useCallback
ensures the function reference stays the same, so the child doesn't re-render unnecessarily. -
2. Event handlers in large data lists:
In a table or list with many rows, handlers likeonClick
oronChange
can be wrapped withuseCallback
to avoid re-creating them for every item on every render. -
3. API call functions passed to custom hooks:
If you're passing a fetch function to a custom hook or effect hook,useCallback
prevents infinite loops or re-fetches due to changing function references. -
4. Timer or debounce function usage:
When setting intervals or using debounce utilities (like lodash debounce),useCallback
prevents creating new versions of the function, which can break the timing behavior. -
5. Managing WebSocket or real-time listeners:
If you’re attaching listeners to WebSocket or EventSource connections, usinguseCallback
ensures the same listener is retained unless a dependency changes. -
6. Functions shared across tabs/components:
If a sidebar or parent shares handlers (like toggling a modal or notification) across multiple components,useCallback
helps prevent updates from re-rendering everything. -
7. Stable handlers for third-party libraries:
Some third-party libraries (e.g., charts, maps, drag-and-drop) require stable callbacks. Wrapping them inuseCallback
ensures proper behavior without re-registering.
💎 Best Implementation with Full Detail
🧩 Use Case: A parent component renders a counter and a child component with a button. We want the child to re-render only if the function it receives as a prop changes.
👎 Without useCallback
(problematic)
function Child({ onClick }) {
console.log("🔄 Child rendered");
return <button onClick={onClick}>Click Me</button>;
}
function Parent() {
const [count, setCount] = useState(0);
// ⚠️ This function is re-created on every render
const handleClick = () => {
alert("Child button clicked");
};
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child onClick={handleClick} />
</>
);
}
Even if we just click the "+1" button in the parent, the Child
re-renders — because handleClick
is a new function on every render.
✅ With useCallback
(optimized)
const Child = React.memo(({ onClick }) => {
console.log("✅ Child rendered");
return <button onClick={onClick}>Click Me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// ✅ Memoized function — stays the same unless dependencies change
const handleClick = useCallback(() => {
alert("Child button clicked");
}, []);
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child onClick={handleClick} />
</>
);
}
Now the Child
component only renders when it actually needs to. handleClick
keeps the same reference thanks to useCallback
, and React.memo
prevents unnecessary renders.
📌 Summary:
- Wrap event handlers in
useCallback
when passing to memoized children. - Keep dependency arrays accurate — include everything used inside the callback.
- Pair with
React.memo
to maximize performance. - Use wisely — don’t over-optimize where not needed.
Custom Hooks
🧠 Detailed Explanation
Custom Hooks in React are just JavaScript functions that start with the word use
and let you reuse stateful logic across components.
Instead of copying and pasting the same useState
, useEffect
, or useContext
logic in every component, you can move that logic into a single function — a custom hook — and use it anywhere.
Think of it like this:
- 🔁 You often repeat the same logic (like fetching data or handling a form).
- 🧩 You can create a hook (e.g.,
useFetchData
) that holds that logic. - 📦 Then reuse that hook in multiple components without rewriting the same code.
Why it’s awesome: It keeps your components clean, avoids repetition, and makes logic reusable and testable.
Important: Custom Hooks must start with use
and they can use other hooks inside them.
💡 Examples
🧪 Example 1: Creating and Using a useCounter
Hook
Imagine you need to implement a counter in multiple places. Instead of repeating useState
and logic, you can create a custom hook:
// useCounter.js
import { useState } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
export default useCounter;
Now use it inside any component:
// CounterComponent.js
import useCounter from './useCounter';
function CounterComponent() {
const { count, increment, decrement, reset } = useCounter(5);
return (
<div>
<h4>Count: {count}</h4>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
✅ Why this helps: You can now reuse useCounter()
in multiple components without duplicating logic. Cleaner, smarter, and easier to test!
🧪 Example 2: useLocalStorage Hook
Let’s say you want to save state to local storage. Instead of writing logic in every component, create a hook:
// useLocalStorage.js
import { useState } from 'react';
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
const setValue = (value) => {
setStoredValue(value);
localStorage.setItem(key, JSON.stringify(value));
};
return [storedValue, setValue];
}
// In any component
const [name, setName] = useLocalStorage("username", "Guest");
✅ Result: Now the name persists even after page refresh — and the hook can be reused anywhere!
🔁 Alternative Concepts
Instead of custom hooks, you could directly use useState
and useEffect
in each component — but this leads to repetition. Custom Hooks centralize logic.
❓ General Questions & Answers
Q1: What is a custom hook?
A: A custom hook is just a regular JavaScript function that starts with use
and uses React hooks (like useState
, useEffect
, etc.) inside it. It lets you reuse logic across different components.
Q2: Why should I use custom hooks?
A: To avoid repeating the same logic in multiple components. For example, if you’re using useState
and useEffect
for data fetching in 5 places, you can move that logic into one custom hook like useFetch()
.
Q3: Do custom hooks return JSX?
A: No. They return values like state, functions, or data — not UI. Components use the hook’s return values and decide what to render.
Q4: Can a custom hook use multiple built-in hooks?
A: Yes! Custom hooks can use as many hooks inside them as needed. For example, a custom hook can use useState
, useEffect
, and useRef
all together.
Q5: What are some examples of custom hooks?
useCounter
– handles increment/decrement logicuseForm
– manages form state and validationuseFetch
– fetches data from an APIuseLocalStorage
– stores state in browser storage
🛠️ Technical Q&A with Examples
Q1: How do I pass parameters to a custom hook?
A: You pass parameters just like a normal function.
// Custom Hook
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
return { count };
}
// Usage
const { count } = useCounter(10);
Q2: Can I use async/await inside a custom hook?
A: Yes, but you must place async logic inside useEffect
or inside a separate function — not directly in the hook's body.
function useFetchData(url) {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const json = await response.json();
setData(json);
}
fetchData();
}, [url]);
return data;
}
Q3: Can a custom hook call another custom hook?
A: Yes! You can compose hooks just like functions. This is common for separating concerns.
// useUser.js
function useUser() {
const { data } = useFetchData('/api/user');
return data;
}
Q4: Can I return multiple values from a custom hook?
A: Yes, use an object or an array to return multiple values.
function useToggle() {
const [on, setOn] = useState(false);
const toggle = () => setOn(prev => !prev);
return [on, toggle];
}
Q5: Why is my custom hook not working correctly inside a loop or condition?
A: Hooks (including custom ones) must be called at the top level of a component or another hook — not inside if
statements, loops, or nested functions.
// ❌ Incorrect
if (condition) {
useMyHook(); // This is invalid
}
// ✅ Correct
const value = useMyHook(); // always call hooks at the top
✅ Best Practices with Examples
1. Start your hook name with use
✅ This is required for React to recognize it as a hook.
// ✅ Correct
function useForm() { ... }
// ❌ Incorrect
function formHook() { ... } // Won't work properly with React
2. Always call custom hooks at the top level of a component
✅ Never place hooks inside conditions or loops. React depends on consistent order.
// ✅ Good
function MyComponent() {
const { value } = useCustomHook();
return <div>{value}</div>;
}
// ❌ Bad
if (loggedIn) {
useCustomHook(); // Wrong usage!
}
3. Keep hooks focused — one job per hook
✅ A hook should handle one concern like fetching, toggling, or form state.
// ✅ Good
function useToggle() { ... }
// ❌ Bad (too much responsibility)
function useAuthAndFetchDataAndTheme() { ... }
4. Return only what is needed
✅ Avoid returning extra values that the component doesn’t use.
// ✅ Return only what's useful
return { count, increment, reset };
5. Extract reusable logic — not UI
✅ Custom hooks are about logic reuse, not JSX reuse.
// ✅ Good
function useTimer() { ... }
// ❌ Bad
function useHeaderComponent() {
return <h1>Header</h1>;
}
6. Combine built-in hooks inside custom hooks
✅ You can mix useState
, useEffect
, useRef
, etc. inside a custom hook.
function useStopwatch() {
const [time, setTime] = useState(0);
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => setTime(t => t + 1), 1000);
return () => clearInterval(intervalRef.current);
}, []);
return time;
}
7. Create hooks that are composable
✅ One custom hook can use another one to compose more powerful logic.
function useUserProfile() {
const user = useUser();
const stats = useUserStats(user.id);
return { user, stats };
}
🌍 7 Real-World Examples
-
useForm
– Manage form state
Handles form inputs, validation, and submission logic in one reusable place.
Used in: Signup forms, comment boxes, search filters. -
useFetch
– Fetch data from APIs
Handles loading, error, and data state from a REST API or GraphQL endpoint.
Used in: Dashboards, profile data, weather apps. -
useDebounce
– Delay a value update
Delays fast-changing values like search input so that API calls are optimized.
Used in: Search bars, filtering tables, auto-suggestions. -
useLocalStorage
– Store data in local storage
Keeps user preferences or form inputs in the browser's local storage.
Used in: Theme switchers, language selectors, drafts. -
usePrevious
– Track previous value
Helps you compare current and previous state or props.
Used in: Animation changes, detecting direction, undo history. -
useWindowSize
– Detect window size changes
Tracks width and height of the browser window in real-time.
Used in: Responsive layouts, conditional rendering. -
useToggle
– Switch between boolean states
Great for toggling modals, dropdowns, themes, or visibility.
Used in: Show/hide password, dark mode, nav menus.
💡 Best Implementation: useFetch
Hook
The useFetch
custom hook helps you fetch data from any API and manage loading, error, and data states in a reusable way.
✅ Why it’s useful:
- Centralizes API fetching logic
- Reduces repetition in components
- Improves readability and maintainability
🛠️ Code:
import { useState, useEffect } from "react";
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true; // Avoid updating state if component unmounted
async function fetchData() {
setLoading(true);
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error("Error: " + response.status);
}
const json = await response.json();
if (isMounted) setData(json);
} catch (err) {
if (isMounted) setError(err.message);
} finally {
if (isMounted) setLoading(false);
}
}
fetchData();
return () => {
isMounted = false; // Cleanup
};
}, [url]);
return { data, loading, error };
}
📦 Example Usage:
function PostList() {
const { data, loading, error } = useFetch("https://jsonplaceholder.typicode.com/posts");
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
✨ Features in this implementation:
- Handles loading and error states
- Supports async/await with cleanup
- Reusable across multiple components
- Safe for unmounted components (avoids memory leaks)
🔁 You can extend it to:
- Support POST, PUT, DELETE methods
- Include authorization headers
- Integrate with retry strategies or caching
Smart (Container) vs Dumb (Presentational) Components
🧠 Detailed Explanation
In React, components can be organized into two simple types to keep your code clean and manageable:
Smart (Container) Components: These components are in charge of data and logic. They fetch data, handle state, and pass that data down to other components. They are called “smart” because they know what's going on and make decisions.
- 🧠 Manages
useState
anduseEffect
- 📡 Fetches data from APIs
- 📤 Sends props to other components
Dumb (Presentational) Components: These components only focus on how things look. They don’t care where data comes from. They just receive it through props and display it. That’s why they’re called “dumb” — but they’re actually super useful!
- 🎨 Only displays UI
- 🧽 Has no state or API logic
- 📩 Uses props to get data and trigger actions
🔁 Think of it like this:
- Smart = Boss 👨💼 – knows what to do, handles data, and tells others what to show
- Dumb = Assistant 🎨 – just follows instructions and presents the results beautifully
This separation makes your code modular, reusable, and easy to debug. You can update logic in the container without touching the UI, and reuse the same presentational component in many places.
💡 Examples
Example 1: Smart Component (Container)
function UserContainer() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(data => setUser(data));
}, []);
return <UserProfile user={user} />;
}
Explanation:
UserContainer
fetches the data, stores it in state, and passes it down to the UserProfile
component. It’s smart because it handles the logic.
Example 2: Dumb Component (Presentational)
function UserProfile({ user }) {
if (!user) return <p>Loading...</p>;
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
</div>
);
}
Explanation:
UserProfile
simply displays the user data passed via props. It does not manage any state or fetch anything. It's a “dumb” component, focused only on the UI.
Example 3: Separation in a Todo App
// Smart
function TodoContainer() {
const [todos, setTodos] = useState([]);
useEffect(() => {
fetch('/api/todos')
.then(res => res.json())
.then(setTodos);
}, []);
return <TodoList items={todos} />;
}
// Dumb
function TodoList({ items }) {
return (
<ul>
{items.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
Explanation:
TodoContainer
handles fetching and passing data.
TodoList
handles how that data is displayed. This separation makes it easier to maintain, test, and reuse components.
🔁 Alternative Approaches
Modern React often uses hooks and context to replace older "container vs presentational" patterns.
❓ General Questions & Answers
Q1: What is the main difference between Smart and Dumb components?
A: Smart components (a.k.a. container components) handle data fetching, state management, and logic. Dumb components (presentational components) are focused on how things look. They receive data via props and display it without knowing where it came from.
Q2: Why should I separate components this way?
A:
Separation improves reusability, testability, and code organization.
You can reuse a dumb component like <UserCard />
in multiple places while only changing the logic in the smart component.
Q3: Is this pattern still valid with Hooks?
A:
Yes! Even with React Hooks, separating logic from UI remains a good practice.
Smart components can use hooks like useState
, useEffect
, etc., while dumb components stick to UI rendering.
Q4: Can a smart component also contain some UI?
A: Technically yes, but it's better to keep logic and presentation separate for clarity. If your component starts mixing logic and layout heavily, consider breaking it apart.
Q5: Do dumb components need to be functional components?
A: Yes, functional components are ideal for presentational use. They are simple, stateless, and just return JSX. You can even use them as pure functions.
🛠️ Technical Q&A with Examples
Q1: How do I pass data from a smart component to a dumb one?
A:
Use props
. Smart components manage state and logic, then pass the needed data and functions down to dumb components.
// Smart Component
function Parent() {
const [count, setCount] = useState(0);
return <CounterDisplay count={count} />;
}
// Dumb Component
function CounterDisplay({ count }) {
return <p>Count is: {count}</p>;
}
Q2: Can a dumb component trigger actions in a smart component?
A: Yes! Pass down callback functions from the smart component to the dumb component.
// Smart
function Parent() {
const [name, setName] = useState("");
const handleChange = (value) => setName(value);
return <InputField value={name} onChange={handleChange} />;
}
// Dumb
function InputField({ value, onChange }) {
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}
Q3: How do I test smart vs dumb components?
A: For dumb components, test rendering and props. For smart components, test business logic and state behavior.
// Dumb Component Test (React Testing Library)
render(<UserProfile name="Aisha" />);
expect(screen.getByText("Aisha")).toBeInTheDocument();
// Smart Component Test
const { result } = renderHook(() => useUserData());
expect(result.current.user).toEqual(mockUser);
Q4: How do I reuse a dumb component in multiple smart containers?
A: Keep the dumb component generic. Pass only what it needs via props.
// Dumb
function Button({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}
// Smart usage 1
<Button label="Save" onClick={saveData} />
// Smart usage 2
<Button label="Delete" onClick={deleteItem} />
Q5: Can I move logic out of smart components?
A:
Yes! Use custom hooks like useUserData()
to extract reusable logic.
// Custom Hook
function useUserData() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user').then(res => res.json()).then(setUser);
}, []);
return user;
}
// Smart Component
function UserContainer() {
const user = useUserData();
return <UserProfile user={user} />;
}
✅ Best Practices with Examples
1. Separate logic from UI
✅ Keep your smart (container) components responsible for logic and data, and dumb (presentational) components responsible for layout and display.
// Smart Component
function UserContainer() {
const [user, setUser] = useState({ name: "Ali", age: 30 });
return <UserProfile user={user} />;
}
// Dumb Component
function UserProfile({ user }) {
return <p>Name: {user.name}</p>;
}
2. Make dumb components stateless (if possible)
✅ Only use props. Avoid internal state unless absolutely necessary.
function Message({ text }) {
return <div>{text}</div>;
}
3. Make dumb components reusable
✅ Use props for all dynamic values. Avoid hardcoded values.
function Button({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}
4. Validate your props
✅ Help prevent bugs by clearly defining what each component expects.
// With PropTypes
UserProfile.propTypes = {
user: PropTypes.shape({
name: PropTypes.string,
age: PropTypes.number
})
};
5. Keep each component focused on one responsibility
✅ Don’t mix data fetching and rendering in the same component.
6. Use custom hooks to extract shared logic
✅ For cleaner container components and better reuse.
function useUser() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch("/api/user").then(res => res.json()).then(setUser);
}, []);
return user;
}
🌍 7 Real-World Examples
-
Authentication Flow:
A container like<LoginContainer />
handles API calls and state management, while<LoginForm />
just displays the inputs and receives props likeonSubmit
. -
Blog Post Display:
<BlogContainer />
fetches blog post data, manages loading states, and passes it down to<PostList />
to render cards. -
Product Page:
<ProductContainer />
fetches product details and availability, then passes it to<ProductDetails />
to render the UI. -
Shopping Cart:
<CartContainer />
handles logic like add/remove items, totals, and passes this to<CartView />
which simply displays the data. -
User Profile Page:
<ProfileContainer />
fetches data and updates it through API.<ProfileCard />
displays the user info and edit button via props. -
Admin Dashboard:
The smart component handles all the analytics queries and passes them to multiple dumb widgets like<StatCard />
or<ChartView />
. -
Notifications System:
<NotificationContainer />
listens to a WebSocket stream or polling API and sends new data to<NotificationList />
for display.
🏗️ Best Implementation (Step-by-Step)
Let's create a practical example of a user profile display using the smart/dumb component pattern. The container will manage state and fetch data, while the presentational component will simply display what it’s given.
1. Create a Presentational (Dumb) Component
// components/UserCard.js
function UserCard({ name, email, onRefresh }) {
return (
<div className="card">
<h3>{name}</h3>
<p>Email: {email}</p>
<button onClick={onRefresh}>Refresh</button>
</div>
);
}
✅ This component is reusable, has no internal state, and only depends on props.
2. Create a Container (Smart) Component
// containers/UserContainer.js
import { useState, useEffect } from "react";
import UserCard from "../components/UserCard";
function UserContainer() {
const [user, setUser] = useState({ name: "", email: "" });
const fetchUser = async () => {
const res = await fetch("/api/user");
const data = await res.json();
setUser(data);
};
useEffect(() => {
fetchUser();
}, []);
return (
<UserCard
name={user.name}
email={user.email}
onRefresh={fetchUser}
/>
);
}
✅ This component handles logic, fetching, state, and passes props to the dumb component.
3. Reuse with Another Component
// App.js or any parent
import UserContainer from "./containers/UserContainer";
function App() {
return (
<div>
<h1>Dashboard</h1>
<UserContainer />
</div>
);
}
✅ This demonstrates how container components can be reused, scaled, or tested independently.
✅ Why this is the Best Practice
- Separation of concerns: Logic and UI are decoupled.
- Reusability: Presentational components can be reused across different containers.
- Testability: Easy to write unit tests for dumb components (pure functions).
- Scalability: Easy to scale app by following the same pattern.
- Maintenance: Cleaner codebase that’s easier to debug and maintain.
🧩 Compound Components in React
🧠 Detailed Explanation
Compound components are a pattern in React where multiple components are used together as a team. One parent component controls how the child components behave — even though the developer using them doesn’t have to manually connect everything.
Imagine you're building a Tabs component. You want to write:
<Tabs>
<Tab>Overview</Tab>
<Tab>Pricing</Tab>
<Tab>Contact</Tab>
</Tabs>
With compound components, the Tabs
component knows how to manage which tab is selected, and it passes that info down to each Tab
automatically.
That means you don’t have to write extra props manually for things like active
or onClick
— the logic is handled inside Tabs
, making your code clean and easier to use.
In short: Compound components help keep your UI flexible and maintainable by letting components work together without repeating yourself.
💡 Examples
Example 1: Custom Tabs using Compound Components
// Tabs.js (Parent Component)
import { createContext, useContext, useState } from "react";
const TabsContext = createContext();
export function Tabs({ children }) {
const [activeIndex, setActiveIndex] = useState(0);
return (
<TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
<div className="tabs-container">{children}</div>
</TabsContext.Provider>
);
}
export function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}
export function Tab({ index, children }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
const isActive = index === activeIndex;
return (
<button
onClick={() => setActiveIndex(index)}
style={{ fontWeight: isActive ? "bold" : "normal" }}
>
{children}
</button>
);
}
export function TabPanel({ index, children }) {
const { activeIndex } = useContext(TabsContext);
return activeIndex === index ? <div className="tab-panel">{children}</div> : null;
}
Usage:
<Tabs>
<TabList>
<Tab index={0}>Overview</Tab>
<Tab index={1}>Pricing</Tab>
<Tab index={2}>Contact</Tab>
</TabList>
<TabPanel index={0}>This is the overview tab.</TabPanel>
<TabPanel index={1}>This is the pricing tab.</TabPanel>
<TabPanel index={2}>This is the contact tab.</TabPanel>
</Tabs>
Explanation:
<Tabs>
manages the state of the current active tab.<TabList>
wraps all the clickable tab buttons.<Tab>
sets the active tab index using shared context.<TabPanel>
only displays content for the selected tab.
Example 2: Compound Component for Accordion
function Accordion({ children }) {
return <div className="accordion">{children}</div>;
}
function AccordionItem({ title, children }) {
const [open, setOpen] = useState(false);
return (
<div className="accordion-item">
<div className="title" onClick={() => setOpen(!open)}>{title}</div>
{open && <div className="content">{children}</div>}
</div>
);
}
// Usage:
<Accordion>
<AccordionItem title="What is React?">React is a UI library.</AccordionItem>
<AccordionItem title="What is JSX?">JSX is syntax for templating in JS.</AccordionItem>
</Accordion>
Explanation: Each item manages its own open/close state, but the overall structure is reusable and declarative.
🔁 Alternative Concepts
- Passing props manually to each child
- Using Context API for shared state (recommended for large trees)
- Using slot-like children patterns
❓ General Questions & Answers
Q1: What is a compound component?
A: A compound component is a pattern in React where a parent component manages state and passes it implicitly to its child components using React Context
. This allows the children to work together without manually passing props through every level.
Example: A tab system with a <Tabs>
wrapper, and inside it <TabList>
, <Tab>
, and <TabPanel>
.
Q2: Why should I use the compound component pattern?
A: It helps to create more flexible and scalable UIs. Instead of tightly coupling logic and UI together, it allows developers to compose complex behavior in a cleaner and more declarative way.
Benefit: It’s easy to add/remove/rearrange child components without breaking the functionality.
Q3: Do compound components require React Context?
A: Not always, but React Context makes it much easier to share state between the parent and deeply nested child components without prop drilling.
Without context: You would have to manually pass state and event handlers down multiple levels.
Q4: How are compound components different from reusable components?
A: Reusable components are standalone pieces you can use anywhere, while compound components are designed to work together and rely on each other. The parent defines the logic; the children define the layout.
Q5: Is compound component a good fit for all UI patterns?
A: No, it's best suited for patterns where components work in a coordinated way — like tabs, modals, accordions, and dropdown menus. For simple UI, traditional composition or reusable component patterns may be better.
🛠️ Technical Q&A with Examples
Q1: How do compound components share state?
A: They usually use React Context
created in the parent component, allowing all children to access and modify shared state without prop drilling.
// TabsContext.js
const TabsContext = React.createContext();
// Tabs.js
function Tabs({ children }) {
const [activeIndex, setActiveIndex] = React.useState(0);
return (
<TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
{children}
</TabsContext.Provider>
);
}
Q2: How do I build a Tab child component that responds to context?
A: You consume the shared context using useContext
and update or read state as needed.
function Tab({ index, children }) {
const { activeIndex, setActiveIndex } = React.useContext(TabsContext);
const isActive = index === activeIndex;
return (
<button
onClick={() => setActiveIndex(index)}
style={{ fontWeight: isActive ? 'bold' : 'normal' }}
>
{children}
</button>
);
}
Q3: Can compound components be reused in multiple parts of my app?
A: Yes! Once you define a compound component (like a Tabs system), you can reuse it anywhere just like reusable components. They are flexible and can be composed differently based on the children passed in.
<Tabs>
<TabList>
<Tab index={0}>Home</Tab>
<Tab index={1}>Profile</Tab>
</TabList>
<TabPanel index={0}>Home content</TabPanel>
<TabPanel index={1}>Profile content</TabPanel>
</Tabs>
Q4: What if I want to use props and children in compound components together?
A: You can use a mix of static configuration via props and dynamic children. This provides flexibility, especially when customizing the behavior or layout of internal parts.
function TabPanel({ index, children }) {
const { activeIndex } = React.useContext(TabsContext);
return activeIndex === index ? <div>{children}</div> : null;
}
Q5: Can I combine compound components with render props or slots?
A: Yes. Advanced patterns can combine compound components with render props to provide even more flexibility.
For example: A Tabs component could allow a custom render function for tabs or panels to override default behavior.
✅ Best Practices with Examples
1. Use React Context to share state between compound components
This allows the parent to manage shared state while children communicate seamlessly.
const MyContext = React.createContext();
function Parent({ children }) {
const [value, setValue] = useState("default");
return (
<MyContext.Provider value={{ value, setValue }}>
{children}
</MyContext.Provider>
);
}
2. Name your child components clearly
Make your component hierarchy easy to understand.
<Tabs>
<TabList>
<Tab>One</Tab>
<Tab>Two</Tab>
</TabList>
<TabPanels>
<TabPanel>Panel 1</TabPanel>
<TabPanel>Panel 2</TabPanel>
</TabPanels>
</Tabs>
3. Allow children to be flexible and composable
This helps users of your compound component rearrange or reuse components easily.
function TabPanel({ children, index }) {
const { activeIndex } = useContext(TabsContext);
return activeIndex === index ? <div>{children}</div> : null;
}
4. Parent component should handle logic, child components should handle presentation
This keeps each part maintainable and reusable.
// Parent
function Tabs({ children }) {
const [index, setIndex] = useState(0);
return (
<TabsContext.Provider value={{ index, setIndex }}>
{children}
</TabsContext.Provider>
);
}
5. Provide sensible defaults or fallbacks
Make it easier to use your component with minimal configuration.
function Tab({ index = 0, children }) {
const { setIndex } = useContext(TabsContext);
return <button onClick={() => setIndex(index)}>{children}</button>;
}
6. Add ARIA roles and keyboard support
This makes compound components more accessible for all users.
<button role="tab" aria-selected="true" aria-controls="tabpanel-1">Tab 1</button>
7. Validate expected children inside parent
You can use React.Children
to ensure the expected structure.
React.Children.forEach(children, child => {
if (child.type !== Tab) {
console.warn("Only children are supported");
}
});
🌍 Real-World Examples
-
Tabs Navigation
A UI with a
<Tabs>
parent and child components like<TabList>
,<Tab>
,<TabPanel>
. Each piece controls a part of the UI, but all share the same state. -
Accordion
Parent
<Accordion>
manages the open/close logic. Each<AccordionItem>
and<AccordionPanel>
behaves independently but uses shared state via context. -
Modal Dialogs
<Modal>
provides layout and animation.<ModalHeader>
,<ModalBody>
, and<ModalFooter>
are child components for structure. -
Dropdown Menu
A reusable
<Dropdown>
component with<DropdownToggle>
and<DropdownMenu>
. The parent manages visibility and interaction. -
Stepper/Progress Wizard
<Stepper>
component wraps<Step>
and<StepContent>
. State like current step is shared via context. -
Form Builder
<Form>
can wrap inputs like<FormField>
,<FormLabel>
, and<FormError>
. Shared logic handles validation and input states. -
Carousel/Slider
<Carousel>
as the parent with<Slide>
,<NextButton>
, and<PrevButton>
. Everything shares state like current slide and transitions.
Controlled vs Uncontrolled Components
🧠 Detailed Explanation
In React, form inputs can be categorized as controlled or uncontrolled depending on how their values are handled.
- Controlled Component: The input’s value is controlled by React state using hooks like
useState
. Any change to the input updates the state, and vice versa. - Uncontrolled Component: The input maintains its own internal state. You access its value using a
ref
instead of storing it in React state.
Controlled components are commonly used in React applications because they offer better control, validation, and predictable behavior. Uncontrolled components are useful for quick prototyping or when integrating non-React libraries.
✅ Key difference: Controlled = React is the single source of truth.
Uncontrolled = DOM is the source of truth.
💡 Examples
Example 1: Controlled Component
import { useState } from 'react';
function ControlledInput() {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<p>You typed: {name}</p>
</div>
);
}
Explanation: The input is bound to the state name
, so React fully controls its value.
Example 2: Uncontrolled Component
import { useRef } from 'react';
function UncontrolledInput() {
const inputRef = useRef();
const handleSubmit = () => {
alert("Value: " + inputRef.current.value);
};
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleSubmit}>Show Value</button>
</div>
);
}
Explanation: The input value is not bound to state — it’s accessed via ref
when needed.
Example 3: Comparison Table
| Feature | Controlled | Uncontrolled |
|-----------------------|-------------------|---------------------|
| Manages State | React (useState) | DOM (input element) |
| Uses Refs | No | Yes |
| Real-time Validation | Easy | Harder |
| Suitable For | Forms, validation | Quick input capture |
🔁 Alternative Methods or Concepts
- You can combine both approaches for flexibility.
- For simple forms, uncontrolled components can be quick and performant.
❓ General Questions & Answers
Q1: What is a controlled component in React?
A: A controlled component is one where form data is handled by React state. You use useState
to manage the value of the input. The component renders the value based on state and updates it on every change.
Q2: What is an uncontrolled component?
A: An uncontrolled component lets the DOM handle the form data. Instead of managing value in React state, you access the input’s value using a ref
(like useRef
) only when needed (e.g., on form submission).
Q3: Which one should I use?
A: It depends:
- ✅ Use controlled components for full control, validation, and UI updates.
- ✅ Use uncontrolled components when you just need to grab a value on submit without triggering re-renders.
Q4: Can I mix both controlled and uncontrolled components?
A: Technically yes, but it’s not recommended. It can lead to inconsistent behavior and difficult-to-maintain code.
Q5: What’s the performance difference?
A: Uncontrolled components can be slightly faster because they don’t re-render on every keystroke. However, controlled components are more predictable and easier to debug in complex UIs.
🛠️ Technical Q&A with Examples
Q1: What happens when you use both `value` and `defaultValue` in an input field?
A: React considers `value` as the source of truth. If you provide both, defaultValue
is ignored and value
will control the input. This can lead to confusion and bugs.
<input value="John" defaultValue="Doe" /> // "Doe" is ignored
Q2: How can you reset a controlled input to its initial state?
A: By setting the state back to the initial value using a function.
const [email, setEmail] = useState("initial@example.com");
const reset = () => setEmail("initial@example.com");
Q3: What happens if you don’t set `value` or `defaultValue`?
A: The input behaves as an uncontrolled component by default. It maintains its own state in the DOM.
<input type="text" /> // uncontrolled
Q4: How can you programmatically update the value of an uncontrolled component?
A: Use a ref
to access and update the DOM node directly.
const inputRef = useRef();
const updateInput = () => {
inputRef.current.value = "New Value";
};
Q5: Can you convert an uncontrolled component into a controlled one during runtime?
A: Technically yes, but it's discouraged. React will warn about switching from uncontrolled to controlled. It's better to pick one pattern and stick to it.
✅ Best Practices
- Use controlled components for consistency and form logic.
- Use uncontrolled for quick inputs or legacy forms where minimal control is needed.
- Never mix both without a clear reason—it can cause bugs.
🌍 Real-world Scenario
In a sign-up form, controlled components ensure input validation, while an uncontrolled file input (like an image upload) can handle the file reference directly via ref
.
Render Props & HOCs (Legacy Patterns)
🧠 Detailed Explanation
Render Props and Higher-Order Components (HOCs) are patterns used in React to share logic between components. These were more common before React Hooks were introduced, but they are still useful in understanding legacy codebases or specific architectural styles.
Render Props is a technique for sharing code using a prop whose value is a function. A component receives a function as a prop, which it calls to render something based on its internal state.
Higher-Order Components (HOCs) are functions that take a component and return a new component with added behavior or props. They are useful for wrapping components with reusable logic (e.g., authentication, logging, subscriptions).
Both are powerful for making code reusable and abstracting common logic, but with the introduction of Hooks, custom hooks have become the preferred way to achieve the same effect with cleaner and more readable code.
🚀 Implementation
Both Render Props and Higher-Order Components (HOCs) were popular before React Hooks came along. They're still useful to understand for working with legacy codebases or libraries.
🔹 Render Props
A Render Prop is a technique where you pass a function as a prop to a component. This function receives data or logic and returns a React element.
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, []);
return render(position);
}
function App() {
return (
<MouseTracker render={(pos) => (
<h1>Mouse at ({pos.x}, {pos.y})</h1>
)} />
);
}
🔹 Higher-Order Component (HOC)
A HOC is a function that takes a component and returns a new component with additional props or functionality.
function withCounter(WrappedComponent) {
return function CounterComponent(props) {
const [count, setCount] = useState(0);
const increment = () => setCount((prev) => prev + 1);
return <WrappedComponent count={count} increment={increment} {...props} />;
};
}
function ClickCounter({ count, increment }) {
return <button onClick={increment}>Clicked {count} times</button>;
}
const EnhancedClickCounter = withCounter(ClickCounter);
Note: While both techniques work well, modern React prefers Hooks and Composition for clarity, testability, and reusability.
💡 Examples
Example 1: Render Props Pattern
function DataProvider({ render }) {
const data = { name: "React" };
return render(data);
}
function App() {
return (
<DataProvider render={(data) => (
<h1>Hello {data.name}</h1>
)} />
);
}
Explanation: The DataProvider
component accepts a function (render prop) and calls it with the data.
Example 2: Higher-Order Component (HOC)
// HOC definition
function withUser(Component) {
return function EnhancedComponent(props) {
const user = { name: "React Learner" };
return <Component {...props} user={user} />;
};
}
// Usage
function Welcome({ user }) {
return <h1>Welcome, {user.name}</h1>;
}
const EnhancedWelcome = withUser(Welcome);
Explanation: The withUser
HOC injects the user
prop into the Welcome
component.
Example 3: Comparing with Custom Hook (Modern Approach)
function useUser() {
return { name: "Modern React" };
}
function Welcome() {
const user = useUser();
return <h1>Welcome, {user.name}</h1>;
}
Explanation: Instead of using render props or HOCs, you can now achieve the same using a custom hook for cleaner code.
🔁 Alternative Methods or Concepts
1. Custom Hooks (Modern Alternative)
Instead of Render Props or HOCs, React Hooks let you extract and reuse stateful logic in a clean and readable way.
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, []);
return position;
}
// Usage
function MouseTracker() {
const { x, y } = useMousePosition();
return <h1>Mouse at ({x}, {y})</h1>;
}
Why it's better: Hooks provide better readability and separation of concerns, making code easier to follow and maintain.
2. Context API (For Shared Data)
If you're using Render Props or HOCs just to pass data deep down, consider using React Context for cleaner prop drilling elimination.
// Create context
const ThemeContext = React.createContext("light");
// Use it in components
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}
Good for: Global themes, authenticated user data, language settings, etc.
❓ General Questions & Answers
Q1: What is a Render Prop?
A: A Render Prop is a function prop passed to a component that returns JSX. It allows dynamic rendering based on the component’s internal state or logic.
<DataProvider render={(data) => <Display data={data} />} />
Q2: What is a Higher-Order Component (HOC)?
A: An HOC is a function that takes a component and returns a new component with added props or behavior. It is used to reuse component logic.
const Enhanced = withLogger(OriginalComponent);
Q3: When should I use Render Props or HOCs?
A: Use them in legacy codebases or when working with libraries built around them. In modern React, prefer Hooks and component composition.
Q4: What are the problems with HOCs?
A: HOCs can lead to "wrapper hell" (too many nested components), unclear prop origins, and naming conflicts. Hooks solve these elegantly.
Q5: Is it okay to mix Render Props and HOCs?
A: Technically yes, but it may lead to confusion. It’s better to pick one pattern per feature or layer and document it clearly in your codebase.
🛠️ Technical Q&A with Examples
Q1: How do you pass data from a render prop to a child component?
A: By invoking the render prop function and passing the data as an argument.
// Render Prop Component
function DataProvider({ render }) {
const data = { user: "Jane" };
return render(data);
}
// Usage
<DataProvider render={(data) => <p>Hello {data.user}</p>} />
Q2: How do you create a Higher-Order Component (HOC)?
A: Create a function that returns a new component with extended functionality.
function withLogger(WrappedComponent) {
return function(props) {
console.log("Rendering:", WrappedComponent.name);
return <WrappedComponent {...props} />;
};
}
Q3: How do you handle props in HOCs?
A: Always pass through all props to the wrapped component to avoid breaking its functionality.
return <WrappedComponent {...props} />;
Q4: Can Render Props work with Hooks?
A: Yes, render props can use Hooks internally. But the parent using the render prop remains unaware of hook usage and keeps logic encapsulated.
Q5: How to avoid prop collisions in HOCs?
A: Prefix custom props (e.g., hocTheme
) or use namespaces inside props to avoid overwriting existing ones.
✅ Best Practices with Examples
1. Use descriptive names for HOCs and render props.
✅ Helps with readability and debugging.
// Bad
function HOC(x) { ... }
// Good
function withAuthorization(WrappedComponent) { ... }
2. Always forward props in HOCs.
✅ Ensures wrapped components receive the props they expect.
function withLogger(WrappedComponent) {
return function(props) {
return <WrappedComponent {...props} />;
}
}
3. Avoid nesting too many HOCs.
⚠️ Can make debugging and component trees very difficult to manage.
4. Prefer render props or hooks for complex logic reuse in modern apps.
✅ Hooks offer better readability and flexibility than nested HOCs.
5. Use memoization (React.memo / useMemo) with HOCs if performance is a concern.
✅ Prevents unnecessary re-renders of deeply wrapped components.
6. Keep render prop components reusable and isolated.
✅ Focus each render prop component on a single concern (e.g., mouse tracking, auth).
7. Document custom HOCs and render props.
✅ Helps future developers understand usage and limitations quickly.
🌍 Real-World Use Cases
- 1. Authentication Wrapper: Use an HOC like
withAuth
to protect routes and redirect unauthenticated users to login. - 2. Theme Provider: A render prop component or HOC can inject dark/light mode values across your app without repeating logic.
- 3. Feature Flag Management: Wrap components using
withFeatureFlag
HOC to show or hide features dynamically based on user roles or AB tests. - 4. Mouse Tracking or Window Resizing: Use render props to provide live window size or mouse position to child components.
- 5. Logging or Analytics: Wrap components in a logger HOC to track user interactions, time spent, or component loads for analytics purposes.
- 6. Permission-Based Rendering: Use render props to conditionally render UI based on user permissions fetched from an API or context.
- 7. Error Boundary Wrapping: Use HOCs to wrap components with an error boundary that gracefully handles component crashes.
Composition Over Inheritance
🧠 Detailed Explanation
In simple terms, composition means building things by combining smaller parts together. Think of it like building with LEGO blocks — you snap different pieces together to make something bigger.
In React, instead of creating big complicated components that "extend" from other components (like using inheritance), we prefer to combine smaller, reusable components using props and children.
This makes your code more flexible, easier to read, and easier to change in the future. You can plug and play different parts without breaking things!
That’s why in React, we say: “Prefer composition over inheritance.”
🛠️ Implementation
Let’s see how you can use composition instead of inheritance in React.
Example: Composing a Page Layout with Reusable Components
// Header.js
function Header() {
return <header><h1>My Site</h1></header>;
}
// Footer.js
function Footer() {
return <footer>© 2025 My Company</footer>;
}
// Layout.js
function Layout({ children }) {
return (
<div>
<Header />
<main>{children}</main>
<Footer />
</div>
);
}
// HomePage.js
function HomePage() {
return (
<Layout>
<p>Welcome to my website!</p>
</Layout>
);
}
✅ Here, instead of creating one huge component or using class-based inheritance, we compose the page using Header
, Footer
, and Layout
components.
🧠 This approach is reusable, maintainable, and follows the React way of doing things!
💡 Examples
Example 1: Reusable Alert Component
// Alert.js
function Alert({ type, children }) {
const style = {
success: { color: 'green' },
error: { color: 'red' }
};
return <div style={style[type]}>{children}</div>;
}
// Usage
<Alert type="success">Profile saved successfully!</Alert>
<Alert type="error">Failed to save profile.</Alert>
Explanation: The Alert
component is generic and reusable. We compose it by passing in content using children
rather than extending it with multiple components for different types.
Example 2: Page with Slots for Custom Content
// Card.js
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}
// Usage
<Card title="Welcome">
<p>Thanks for joining our platform!</p>
</Card>
Explanation: We don’t need to make a new component for every variation of the card — we just “compose” new content into the same structure.
Example 3: Toolbar with Composition
// Toolbar.js
function Toolbar({ children }) {
return <div className="toolbar">{children}</div>;
}
// Usage
<Toolbar>
<button>Save</button>
<button>Cancel</button>
</Toolbar>
Explanation: Instead of making a “SaveToolbar” and “CancelToolbar”, we use the same Toolbar
layout and just inject different content into it.
🔁 Alternatives
- Render Props: Pass render logic via props.
- HOCs (Higher-Order Components): Enhance behavior by wrapping components.
❓ General Questions & Answers
Q1: What is composition in React?
A: Composition means building components using other components. Instead of extending a base class (like in inheritance), you create reusable parts (components) and combine them to make flexible UIs. It’s like putting together LEGO blocks.
Q2: Why is composition preferred over inheritance in React?
A: React embraces composition because it keeps your components small, reusable, and easier to test. Inheritance creates tight coupling and deep hierarchies, which can make debugging and scaling more difficult.
Q3: Can you give a real-life comparison?
A: Think of composition like a burger — you can add or remove ingredients easily (components). Inheritance is like a pre-made sandwich where changing one layer might require recreating the whole sandwich.
Q4: Is there any case where inheritance is better?
A: In React, composition almost always wins. Inheritance might be useful in very rare cases like building frameworks or libraries, but not in everyday React development.
Q5: How does composition help with reusability?
A: By breaking down UI into small parts (header, sidebar, button, card), you can reuse them across different parts of your app without rewriting code. Each piece is responsible for only one thing, making it more flexible.
🛠️ Technical Q&A with Examples
Q1: How do you implement composition in React?
A: You can pass components as children or props to other components to compose them.
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="content">{children}</div>
</div>
);
}
function App() {
return (
<Card title="Profile">
<p>This is the user profile.</p>
</Card>
);
}
Explanation: The Card
component is composed with whatever is passed as children
.
Q2: Can you pass multiple components inside one component?
A: Yes. React allows multiple components to be passed as children. You can even define custom layout areas using props
.
function Layout({ header, sidebar, main }) {
return (
<div>
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{main}</main>
</div>
);
}
function App() {
return (
<Layout
header={<h1>Welcome</h1>}
sidebar={<ul><li>Home</li></ul>}
main={<p>Main content here</p>}
/>
);
}
Explanation: This allows for greater flexibility and reusable page templates.
Q3: How does composition help avoid prop drilling?
A: With composition, you can use context providers at the top level and inject functionality where needed, instead of passing props deeply through the tree.
Q4: What’s the difference between composition and Higher Order Components (HOCs)?
A: HOCs are functions that take a component and return a new one with enhanced behavior. Composition is more declarative — combining simple components to make complex ones.
// HOC pattern
function withLogger(WrappedComponent) {
return function Enhanced(props) {
console.log('Props:', props);
return <WrappedComponent {...props} />;
};
}
Q5: Can functional components use composition effectively?
A: Yes. In fact, modern React encourages functional components and hooks for clean composition patterns — especially with custom hooks and slot-based layouts.
✅ Best Practices with Examples
1. Prefer composition over inheritance when building UI components
✅ Instead of extending classes to add behavior, use smaller reusable components and combine them.
// ✅ Composition
function Modal({ title, children }) {
return (
<div className="modal">
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}
function LoginForm() {
return (
<Modal title="Login">
<form>
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
</form>
</Modal>
);
}
2. Use custom hooks for logic reuse instead of base component classes
✅ Custom hooks make logic composable and testable.
// Custom Hook
function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount(c => c + 1);
return { count, increment };
}
// Usage
function Counter() {
const { count, increment } = useCounter();
return <button onClick={increment}>Count: {count}</button>;
}
3. Keep components focused and do one thing well
✅ A component should either display UI or handle logic — not both.
// Presentation component
function UserCard({ name, email }) {
return (
<div>
<h4>{name}</h4>
<p>{email}</p>
</div>
);
}
// Container component
function UserContainer() {
const user = { name: "Aisha", email: "aisha@example.com" };
return <UserCard {...user} />;
}
4. Avoid tightly coupling components via inheritance
✅ Composition is more flexible and avoids inheritance hierarchies.
5. Use children
or render props for custom content
✅ Let parent components inject dynamic content into reusable wrappers.
function Wrapper({ children }) {
return <div className="wrapper">{children}</div>;
}
function Page() {
return (
<Wrapper>
<h1>Dashboard</h1>
<p>Welcome user!</p>
</Wrapper>
);
}
🌍 Real-world Use Cases
- ✅ UI Layout Wrappers: Components like
<Card>
,<Modal>
, and<Tooltip>
use composition to wrap dynamic children content. - ✅ Form Layouts: Reusable form field components are composed together rather than extending a base form class.
- ✅ Design Systems: In component libraries like Material UI or Chakra UI, atomic components are composed to create more complex ones.
- ✅ Theming: A
<ThemeProvider>
can wrap any part of the app, passing down styles using composition instead of inheritance. - ✅ Authenticated Layouts: Wrapping authenticated pages with
<AuthLayout>
instead of subclassing protected components. - ✅ Page Templates: Instead of duplicating layout code, components are passed into a common layout wrapper via props or children.
- ✅ Plugins or Extension Points: Like passing UI controls, tools, or widgets into a dashboard component that simply renders whatever is passed into it.
Context API
🧠 Detailed Explanation
The Context API in React is a way to share data across many components without having to pass props manually through every level of the component tree.
Imagine you have a theme (like dark or light mode) or a logged-in user that you want many components to use. Instead of passing that data as a prop again and again, you can use the Context API to make it available everywhere in your app.
It works in three simple steps:
- Create a context: This is like setting up a shared storage area.
- Wrap components in a provider: This makes the data available to all components inside it.
- Use the context: Any component can use the data using
useContext()
.
This makes your code cleaner and easier to manage, especially in large apps.
🛠️ Implementation
To implement the Context API in a real project, follow these clear steps:
- Create a Context: This acts like a central storage for your shared data.
- Wrap your app or component tree with the Provider:
- Consume the Context using
useContext
hook:
import { createContext } from 'react';
export const ThemeContext = createContext();
import React, { useState } from 'react';
import { ThemeContext } from './ThemeContext';
export default function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<MainComponent />
</ThemeContext.Provider>
);
}
import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
function ThemeSwitcher() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
);
}
✅ Tip: You can combine Context API with custom hooks to make consumption even cleaner and reusable.
💡 Examples
Example: Theme Context (Light / Dark Mode)
// 1. Create the context
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
// 2. Provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme((prev) => prev === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
Explanation: This sets up a context with theme
and toggleTheme
available to all child components.
3. Use Context in Any Child Component
function ThemeButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
);
}
Explanation: This component uses the context values to show and change the theme without receiving props.
4. Use in App Component
function App() {
return (
<ThemeProvider>
<Header />
<ThemeButton />
<Footer />
</ThemeProvider>
);
}
Explanation: Now every component inside ThemeProvider
has access to the theme
data.
🔁 Alternatives
Before Context API, developers used libraries like Redux or passed props manually down the component tree. For lightweight needs, you can also use custom hooks or Zustand.
❓ General Questions & Answers
Q1: What is the Context API in React?
A: The Context API is a built-in feature in React that allows you to share state and data across your entire application without passing props manually at every level.
It’s useful for values that are global for a tree of components (like themes, authenticated users, language settings, etc.).
Q2: Why use Context instead of props?
A: Props are great for passing data from parent to child, but when many nested components need the same data, "prop drilling" becomes messy. Context avoids that by allowing direct access to shared state from any component within the Provider's tree.
Q3: Does using Context trigger re-renders?
A: Yes. Any time the context value changes, all consuming components will re-render. To avoid unnecessary re-renders, you can memoize the context value or split contexts by data domain.
Q4: Can Context API be used with other state managers like Redux?
A: Yes, Context can be used alongside Redux, Zustand, or Recoil. It’s common to use Context for global UI state (like dark mode) and a state manager for complex business logic.
Q5: Is Context API suitable for large-scale applications?
A: It can be, but if you need fine-grained performance optimization or complex state logic, you might prefer a dedicated state manager like Redux Toolkit or Zustand.
🛠️ Technical Q&A with Examples
Q1: How do you create and use a simple Context in React?
A: You create a context using createContext
, wrap your components with a Provider
, and consume it using useContext
.
// context.js
import { createContext, useContext, useState } from 'react';
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
return useContext(ThemeContext);
}
// App.js
import { ThemeProvider } from './context';
function App() {
return (
<ThemeProvider>
<HomePage />
</ThemeProvider>
);
}
// HomePage.js
import { useTheme } from './context';
function HomePage() {
const { theme, setTheme } = useTheme();
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme('dark')}>Dark Mode</button>
</div>
);
}
Q2: How can you avoid unnecessary re-renders with Context?
A: Use useMemo
to memoize the context value.
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
Q3: How do you consume multiple Contexts in a single component?
A: Use multiple useContext
hooks.
const { theme } = useContext(ThemeContext);
const { user } = useContext(UserContext);
Or, if performance is a concern, combine them in a custom hook or provider.
Q4: Can you update context from a deeply nested component?
A: Yes. As long as the component is within the Provider
, it can access and update context values using useContext
.
Q5: What happens if a component uses a context without a Provider?
A: The component receives the default value passed to createContext()
. This is helpful for testing or fallback behavior but can be misleading if not intentional.
✅ Best Practices with Examples
1. Memoize Context Values
⚠️ If you pass a new object/function every time, all children re-render even if the value didn’t change. Use useMemo
.
const value = useMemo(() => ({ theme, setTheme }), [theme]);
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
2. Use Context for Shared Data Only
✅ Use context for global/shared data like authentication, theme, user settings — not for everything.
// Good
const ThemeContext = createContext();
// ❌ Don’t store every small local state in context
3. Group Related Contexts Together
If your app uses multiple contexts (e.g., ThemeContext
, UserContext
), consider wrapping them in a single provider tree:
function AppProviders({ children }) {
return (
<UserProvider>
<ThemeProvider>
{children}
</ThemeProvider>
</UserProvider>
);
}
4. Avoid Deep Nesting with Multiple Contexts
⚠️ Using nested .Consumer
components is hard to read and maintain. Prefer useContext
in functional components.
5. Use Default Context Values
✅ Set a default value in createContext
so that your component doesn’t break outside the provider.
const ThemeContext = createContext({ theme: 'light', setTheme: () => {} });
6. Wrap useContext in a Custom Hook
✅ Improves reusability and consistency.
export function useTheme() {
return useContext(ThemeContext);
}
7. Document the Purpose of Each Provider
🧠 This helps other developers understand what data is being shared and used in the app.
🌍 Real-World Use Cases
- 1. Theme Switching: Store light/dark theme preferences and make them accessible across components.
- 2. User Authentication: Keep user login state, access tokens, and profile info available throughout the app.
- 3. Language/Locale Preferences: Store the selected language or region to support internationalization (i18n).
- 4. Shopping Cart: Maintain a global shopping cart accessible from product pages, headers, and checkout components.
- 5. Notification System: Use context to handle app-wide toasts or alert messages triggered from any component.
- 6. Modal Manager: Open and close different modals from anywhere in the app using a global modal context.
- 7. Feature Toggles: Enable or disable parts of the UI conditionally based on flags stored in a global context.
Redux Toolkit (Modern Redux)
🧠 Detailed Explanation
Redux Toolkit is the official, recommended way to use Redux. It simplifies Redux by removing boilerplate code and adding helpful tools like createSlice
and createAsyncThunk
.
Instead of writing separate action types, action creators, and reducers, Redux Toolkit lets you create all of that in one place — inside a "slice."
For example, if you're building a user profile page, you can create a userSlice that manages all user-related state (data, loading, errors) and automatically generates actions and reducers.
It also helps manage asynchronous operations like API calls using createAsyncThunk
. This means you don't need to manually dispatch loading, success, and error actions — Redux Toolkit handles that for you.
- ✅ Clean and concise syntax
- ✅ Great for large apps and scalable architecture
- ✅ Built-in best practices (like immutable updates)
🔁 Think of Redux Toolkit as a faster, safer, and smarter way to write Redux code.
🚀 Best Implementation
The best way to implement Redux Toolkit is by organizing your application into clearly defined slices of state and using createSlice()
and createAsyncThunk()
for synchronous and asynchronous logic. Below is a detailed example:
📦 Step-by-step Setup
- Create a Slice for your domain logic:
- Configure the store with your slice:
- Connect the Redux logic to your component:
// features/user/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
export const fetchUser = createAsyncThunk('user/fetchUser', async (id) => {
const response = await axios.get(`/api/users/${id}`);
return response.data;
});
const userSlice = createSlice({
name: 'user',
initialState: { data: {}, loading: false, error: null },
reducers: {
clearUser: (state) => {
state.data = {};
}
},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});
export const { clearUser } = userSlice.actions;
export default userSlice.reducer;
// app/store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from '../features/user/userSlice';
export const store = configureStore({
reducer: {
user: userReducer,
}
});
// components/UserProfile.js
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUser, clearUser } from '../features/user/userSlice';
function UserProfile({ userId }) {
const dispatch = useDispatch();
const { data, loading, error } = useSelector(state => state.user);
useEffect(() => {
dispatch(fetchUser(userId));
return () => dispatch(clearUser());
}, [dispatch, userId]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>{data.name}</h2>
<p>Email: {data.email}</p>
</div>
);
}
✅ This implementation follows the best Redux Toolkit conventions:
- Slices are kept modular and domain-focused.
- Async logic is handled cleanly using
createAsyncThunk
. - State is managed and updated in a predictable and testable way.
💡 Examples
Example 1: A Simple Counter Slice
// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1 },
decrement: state => { state.value -= 1 },
reset: state => { state.value = 0 }
}
});
export const { increment, decrement, reset } = counterSlice.actions;
export default counterSlice.reducer;
🔍 Explanation: This slice contains the state and reducers. Redux Toolkit uses Immer under the hood, so we can safely "mutate" the state directly.
Example 2: Using the Slice in a Component
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
✅ We use useSelector
to get the count and useDispatch
to trigger actions. Everything stays clean and simple!
Example 3: Async API Call with createAsyncThunk
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// Async thunk to fetch users
export const fetchUsers = createAsyncThunk('users/fetchUsers', async () => {
const response = await fetch('https://api.example.com/users');
return await response.json();
});
const usersSlice = createSlice({
name: 'users',
initialState: { data: [], status: 'idle', error: null },
extraReducers: builder => {
builder
.addCase(fetchUsers.pending, state => {
state.status = 'loading';
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
}
});
🚀 This is a full async flow: loading, success, and error — handled with just one function.
🔁 Alternative Concepts
Before Redux Toolkit, developers manually created actions, action types, and reducers, leading to verbose and complex code. RTK replaces that with concise APIs.
❓ General Questions & Answers
Q1: What is Redux Toolkit and why should I use it?
A: Redux Toolkit is the official, recommended way to use Redux. It simplifies Redux logic by:
- ✅ Reducing boilerplate code
- ✅ Including powerful utilities like
createSlice
andcreateAsyncThunk
- ✅ Using Immer.js under the hood to allow "mutating" state safely
Q2: Do I still need to write action types and creators?
A: No. Redux Toolkit generates action types and creators for you automatically when you use createSlice
. You just write the reducer logic.
Q3: Is Redux Toolkit better than Context API?
A: It depends on your use case:
- 🔹 Use Context API for small apps or simple state (like theme or auth).
- 🔹 Use Redux Toolkit for larger apps with complex state logic, async calls, or shared state across many components.
Q4: What is the difference between Redux and Redux Toolkit?
A: Redux Toolkit is built on top of Redux. It’s like an enhanced version that:
- 🔹 Sets up a store faster
- 🔹 Reduces boilerplate
- 🔹 Encourages good practices
- 🔹 Makes async logic easier with built-in helpers
Q5: Can I migrate an existing Redux app to Redux Toolkit?
A: Yes! You can migrate piece-by-piece:
- 🔁 Convert old reducers to slices using
createSlice
- 💡 Use
configureStore
to replace your existingcreateStore
- ⚙️ Add
createAsyncThunk
for handling API calls cleanly
🛠️ Technical Q&A with Examples
Q1: How do I create a slice using Redux Toolkit?
A: Use createSlice
to define state, reducers, and action creators in one place.
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1 },
decrement: state => { state.value -= 1 },
addAmount: (state, action) => { state.value += action.payload }
}
});
export const { increment, decrement, addAmount } = counterSlice.actions;
export default counterSlice.reducer;
Q2: How do I use createAsyncThunk
for async actions?
A: Use createAsyncThunk
to handle API calls in Redux Toolkit.
import { createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUsers = createAsyncThunk('users/fetch', async () => {
const response = await fetch('https://api.example.com/users');
return response.json();
});
Then use it in a slice:
import { createSlice } from '@reduxjs/toolkit';
import { fetchUsers } from './usersThunks';
const usersSlice = createSlice({
name: 'users',
initialState: { data: [], status: 'idle' },
extraReducers: builder => {
builder
.addCase(fetchUsers.pending, state => {
state.status = 'loading';
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchUsers.rejected, state => {
state.status = 'failed';
});
}
});
export default usersSlice.reducer;
Q3: How do I configure the Redux store using Redux Toolkit?
A: Use configureStore
instead of createStore
.
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer
}
});
export default store;
Q4: How do I connect components with Redux Toolkit?
A: Use useSelector
to read from the store and useDispatch
to dispatch actions.
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './counterSlice';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>Increment</button>
</div>
);
}
Q5: What is Immer and how does it help?
A: Redux Toolkit uses Immer internally. It allows writing "mutating" code in reducers while keeping state immutable.
This makes reducers clean and readable:
// Instead of return { ...state, value: state.value + 1 }
state.value += 1;
✅ Best Practices with Examples
1. Use createSlice
to reduce boilerplate
Why: It automatically generates action creators and action types, keeping code clean.
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1 },
}
});
2. Use createAsyncThunk
for async logic
Why: Keeps side effects out of components and manages loading/error states automatically.
export const fetchPosts = createAsyncThunk('posts/fetch', async () => {
const response = await fetch('/api/posts');
return response.json();
});
3. Always separate UI logic and state logic
Why: Keeps components clean and encourages reusable logic.
const value = useSelector(state => state.counter.value);
const dispatch = useDispatch();
4. Organize slice files modularly
Structure: Feature folders like /features/posts/postsSlice.js
keep things modular and scalable.
5. Use selectors for reusable state access
Why: Makes your state queries reusable across components.
export const selectPostById = (state, postId) =>
state.posts.items.find(post => post.id === postId);
6. Normalize data using entities when needed
Why: Makes it easier to manage large lists or relationships.
Consider libraries like normalizr
or structure your slice like:
{
users: {
byId: {
1: { id: 1, name: 'John' },
2: { id: 2, name: 'Jane' }
},
allIds: [1, 2]
}
}
7. Avoid unnecessary state nesting
Why: Flattened state makes selectors and reducers easier to write and debug.
🌍 Real-World Use Cases
- 1. Shopping Cart Management: Use Redux Toolkit to manage cart items, total amount, and discounts globally across multiple pages.
- 2. Authentication: Handle user login/logout, tokens, and role-based access with global state using `createSlice` and `createAsyncThunk`.
- 3. Notifications System: Display toast notifications across the app by dispatching notification actions from any component.
- 4. Product Listing with Filters: Store filters (like price range, category, and rating) and update them globally to reflect changes instantly.
- 5. Chat Application: Store messages and user activity globally; use middleware for socket-based updates.
- 6. Blog Editor or CMS: Manage the state of draft articles, preview changes, and auto-save content using thunks and slices.
- 7. Real-time Dashboard: Fetch and update real-time analytics data (e.g., sales, views) using Redux async actions and centralized state.
Global State Best Practices
📘 Detailed Explanation
Global state refers to shared data that needs to be accessed and updated by multiple components across your application. This includes things like user authentication, themes, cart items in e-commerce apps, or any data that is reused across screens.
In modern React, global state can be managed efficiently using libraries like:
- Redux Toolkit: A powerful and scalable solution with structured data flow.
- Context API: Suitable for small to medium applications where only a few states are shared globally.
- Zustand, Jotai, Recoil: Simpler alternatives for React state without boilerplate.
The key benefit of using global state is consistency — the UI stays in sync no matter where or how the data is accessed. But it comes with a cost if overused: too much global state can lead to unnecessary re-renders and complexity.
Think of global state as a shared notebook — everyone can write in it and read from it, but you need to organize and protect it properly.
That’s why managing it properly using tools like Redux Toolkit, best practices for memoization, selectors, and using slices is crucial.
🧩 Best Implementation with Detail
Using Redux Toolkit: A recommended approach for scalable, maintainable global state management in modern React apps.
Here's how you can implement global state management using @reduxjs/toolkit
with best practices:
- Step 1: Install Redux Toolkit and React-Redux
- Step 2: Create a slice (e.g., for user state)
- Step 3: Configure the store
- Step 4: Provide the store in your app root
- Step 5: Use global state in components
npm install @reduxjs/toolkit react-redux
// features/userSlice.js
import { createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: {
name: null,
email: null,
isLoggedIn: false,
},
reducers: {
login: (state, action) => {
state.name = action.payload.name;
state.email = action.payload.email;
state.isLoggedIn = true;
},
logout: (state) => {
state.name = null;
state.email = null;
state.isLoggedIn = false;
}
}
});
export const { login, logout } = userSlice.actions;
export default userSlice.reducer;
// app/store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from '../features/userSlice';
export const store = configureStore({
reducer: {
user: userReducer
}
});
// index.js or main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './app/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
// In any component
import { useSelector, useDispatch } from 'react-redux';
import { login, logout } from '../features/userSlice';
function Profile() {
const user = useSelector((state) => state.user);
const dispatch = useDispatch();
return (
<div>
<p>Welcome, {user.name}</p>
<button onClick={() => dispatch(logout())}>Logout</button>
</div>
);
}
🔁 Alternative: Use Zustand or Context API for smaller apps or simpler needs.
🧠 Tip: Structure your Redux slices into domain folders (e.g., `features/user`, `features/cart`) and avoid storing component-specific states globally.
💡 Examples
Example 1: Using Context API for Theme Switching
// ThemeContext.js
const ThemeContext = React.createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Usage in a component
const { theme } = useContext(ThemeContext);
return <div className={theme}>Hello</div>;
Explanation: Theme data is stored globally and accessed anywhere using useContext
.
Example 2: Global Cart using Redux Toolkit
// cartSlice.js
const cartSlice = createSlice({
name: "cart",
initialState: [],
reducers: {
addToCart(state, action) {
state.push(action.payload);
},
removeFromCart(state, action) {
return state.filter(item => item.id !== action.payload);
},
},
});
// Component usage
const dispatch = useDispatch();
dispatch(addToCart({ id: 1, name: "Shirt", qty: 1 }));
Explanation: Redux Toolkit manages cart items as global state accessible by any component.
Example 3: Zustand for Global Authentication State
import create from 'zustand';
const useAuthStore = create((set) => ({
user: null,
login: (userData) => set({ user: userData }),
logout: () => set({ user: null }),
}));
// Usage in a component
const { user, login, logout } = useAuthStore();
Explanation: Zustand offers a simpler and lightweight way to manage global states like login info.
🔁 Alternatives
- Redux Toolkit: Great for large apps with complex state logic.
- Zustand: Lightweight and modern solution with fewer boilerplates.
- Recoil or Jotai: Ideal for fine-grained reactivity and flexibility.
❓ General Questions & Answers
Q1: Why do we need global state in React applications?
A: Global state allows different components across the app to access and update shared data. Without it, you would have to pass props through many layers (prop drilling), which is inefficient and hard to manage. Common examples include user authentication, theme preference, and cart data.
Q2: When should I use global state vs local state?
A: - Use local state when the data is only needed in a single component (e.g., toggling a modal). - Use global state when multiple components need to access or update the same data (e.g., authentication, notifications, settings).
Q3: What are the popular tools to manage global state?
A: React provides the Context API out of the box. For more scalable solutions, developers use Redux Toolkit, Zustand, Jotai, or Recoil depending on their app’s complexity and size.
Q4: Can global state make an app slower?
A: Yes, if not managed properly. Updating global state can cause unnecessary re-renders in components that don’t need the new data. To avoid this, you should split the state into smaller slices and use tools like useMemo
, useCallback
, or selectors.
Q5: Is it a bad practice to put everything in global state?
A: Yes. It’s best to only put shared or cross-component data in the global state. Keeping all state in the global store increases complexity and can make the app harder to debug and maintain.
🛠️ Technical Q&A with Examples
Q1: How do you avoid unnecessary re-renders with global state?
A: Use selectors or memoized values to extract only the parts of the state that a component needs. This ensures that a component re-renders only when its relevant data changes.
// Redux Toolkit Example
const userName = useSelector((state) => state.user.name); // good
// Avoid selecting entire user object unless needed
✅ Solution: Select only the smallest required piece of state.
Q2: What’s the best way to update deeply nested global state?
A: Use tools like immer
(built into Redux Toolkit) to make immutable updates simpler.
// Example reducer in Redux Toolkit
updateUser(state, action) {
state.profile.details.age = action.payload; // immer handles immutability
}
Q3: How can you share state between deeply nested components without prop drilling?
A: Use the Context API or global state libraries like Redux or Zustand.
// Create context
const ThemeContext = React.createContext();
// Use context in nested components
const ThemeToggler = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
return <button onClick={toggleTheme}>Switch to {theme === 'light' ? 'dark' : 'light'}</button>;
};
Q4: How can you manage global state persistently across page reloads?
A: Use middleware like redux-persist
or manually sync to localStorage
in effects.
// Simple example with useEffect
useEffect(() => {
localStorage.setItem("cart", JSON.stringify(cartItems));
}, [cartItems]);
Q5: How do you scope global state to only part of your app?
A: You can create scoped Context Providers that wrap only parts of your component tree.
// Example: Notification context for just the Dashboard section
<DashboardNotificationProvider>
<Dashboard />
</DashboardNotificationProvider>
✅ Best Practices with Examples
1. Keep global state minimal
✅ Only store data globally that needs to be accessed in multiple places. Keep local UI state (like modals, form inputs) inside the component.
// ✅ Good: Only user auth data is global
const user = useSelector((state) => state.auth.user);
// ❌ Avoid: Storing form field values in global state
2. Normalize state shape like a database
✅ Store entities like users or products as objects with IDs for quick access and updates.
state = {
users: {
byId: {
1: { id: 1, name: "Alice" },
2: { id: 2, name: "Bob" }
},
allIds: [1, 2]
}
};
3. Use selectors to encapsulate state access
✅ Makes state easier to refactor and reuse.
// Selector function
const selectUserName = (state) => state.user.name;
const name = useSelector(selectUserName);
4. Avoid unnecessary re-renders by memoizing components
✅ Use React.memo
, useMemo
, or useCallback
to optimize performance.
const MyComponent = React.memo(({ value }) => {
return <div>{value}</div>;
});
5. Persist only essential global state
✅ Avoid persisting unnecessary or sensitive state like UI state or session tokens directly.
// Good with redux-persist
persistReducer({ key: 'auth', storage }, authReducer)
6. Use modern libraries like Redux Toolkit or Zustand
✅ They simplify boilerplate and promote best patterns out of the box.
7. Handle loading and error state globally
✅ Makes UI consistent and easier to debug.
state = {
products: {
data: [],
loading: false,
error: null
}
}
🌍 7 Real-World Use Cases
- 1. User Authentication: Store user login status, JWT tokens, and user data in global state for access across protected routes.
- 2. Shopping Cart in E-commerce App: Track items added to the cart globally to display across pages and handle checkout logic.
- 3. Theme Toggle (Light/Dark Mode): Maintain the user’s selected theme preference in global state and apply it across the app.
- 4. Notifications and Alerts: Display global notifications like success/error messages from a centralized system.
- 5. Multi-Step Forms: Save form data from step-by-step wizards to global state so users can navigate back and forth without losing input.
- 6. Real-Time Chat App: Use global state to track current user, active conversation, unread messages count, and online status of users.
- 7. Feature Flags & Permissions: Store user roles and app feature toggles globally to conditionally render features or access levels throughout the application.
React Router v6+
🧠 Detailed Explanation
React Router v6+ is a powerful library that allows you to handle routing — switching between pages — in your React application without reloading the page. Let's break down the important components:
📦 <BrowserRouter>
– The Router Provider
It wraps your entire app and enables routing functionality using the browser's history API. You typically wrap this at the top level (like inside App.js
or index.js
).
<BrowserRouter>
<App />
</BrowserRouter>
🗺️ <Routes>
– Route Container
This replaces the old <Switch>
from v5. It checks the current URL and finds the first matching <Route>
to render.
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
📍 <Route>
– Defining a Route
Each <Route>
matches a path in the URL and renders the component passed to the element
prop.
- path
: the URL pattern
- element
: the React component to render
<Route path="/contact" element={<Contact />} />
🏗️ <Link>
– Navigation Without Reload
Use <Link>
instead of <a>
to navigate without reloading the page. It updates the URL and re-renders the page component.
<Link to="/about">About Page</Link>
🧱 <Outlet>
– For Nested Routing
<Outlet>
acts as a placeholder for nested routes. If a parent route matches, it renders its layout and inserts child components where the <Outlet>
is placed.
<Route path="dashboard" element={<DashboardLayout />}>
<Route path="stats" element={<Stats />} />
</Route>
Inside DashboardLayout
:
return (
<div>
<Sidebar />
<Outlet /> <!-- This is where nested pages render -->
</div>
)
🔁 Route Parameters (:id
)
You can create dynamic routes using :param
. For example, /user/:id
lets you match any user ID.
<Route path="/user/:id" element={<UserProfile />} />
Inside UserProfile
, you can access id
like this:
import { useParams } from "react-router-dom";
const { id } = useParams();
❌ <Navigate />
– Redirects
Use <Navigate />
to programmatically redirect users from one route to another.
<Route path="/" element={<Navigate to="/home" />} />
All of these tools work together to make your React application feel like a true single-page app (SPA), with clean navigation and smooth transitions.
⚙️ Implementation
Step 1: Install React Router
npm install react-router-dom
Step 2: Set up the router in your App.js
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Step 3: Create the Page Components
// Home.js
export default function Home() {
return <h2>Welcome to the Home Page</h2>;
}
// About.js
export default function About() {
return <h2>About This App</h2>;
}
Step 4: Add Navigation (Optional but useful)
import { Link } from "react-router-dom";
function Navbar() {
return (
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</nav>
);
}
Done! You now have a working multi-page React app using React Router v6+
💡 Examples
Example 1: Basic Routing with <Routes>
and <Route>
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './Home';
import About from './About';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
Explanation: When the URL is /
, the Home
component is shown. When it's /about
, the About
component is rendered.
Example 2: Navigation using <Link>
import { Link } from 'react-router-dom';
function Navbar() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
);
}
Explanation: Clicking on these links will change the URL and render the appropriate component without reloading the page.
Example 3: Nested Routes with <Outlet>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route path="stats" element={<Stats />} />
<Route path="reports" element={<Reports />} />
</Route>
DashboardLayout.js
import { Outlet } from 'react-router-dom';
function DashboardLayout() {
return (
<div>
<h2>Dashboard</h2>
<Outlet />
</div>
);
}
Explanation: This allows multiple sub-routes (like stats and reports) to be nested within the dashboard layout.
Example 4: Redirect with <Navigate />
<Route path="/" element={<Navigate to="/home" />} />
Explanation: When a user visits /
, they are redirected to /home
.
Example 5: Route Parameters
<Route path="/product/:id" element={<ProductPage />} />
import { useParams } from 'react-router-dom';
function ProductPage() {
const { id } = useParams();
return <p>Product ID: {id}</p>;
}
Explanation: You can access dynamic parts of the URL using useParams()
.
🔁 Alternatives
- Next.js Routing (for file-based routes)
- Reach Router (older alternative)
❓ General Questions & Answers
Q1: What is React Router used for?
A: React Router is a standard library for routing in React. It enables navigation among views, lets you change the browser URL, and keeps the UI in sync with the URL—without reloading the page.
Q2: What's the difference between <Routes>
and <Route>
?
A: <Routes>
is a wrapper that holds one or more <Route>
components. Each <Route>
defines a specific path and the element to show. In v6, element
replaces the old component
or render
props.
Q3: How do I create a simple link between pages?
A: Use the <Link to="/about">
component to navigate between pages without full reloads. It's similar to an anchor tag but built for React routing.
Q4: What happens when a route doesn’t match?
A: You can define a fallback route using path="*"
which shows a 404 or “Page not found” message when no route matches.
Q5: Can I redirect users using React Router v6?
A: Yes. Use the <Navigate />
component inside your route configuration to redirect users to a different route programmatically.
🛠️ Technical Q&A with Examples
Q1: How do I create nested routes in React Router v6?
A: Use nested <Route>
components inside a parent <Route>
and render <Outlet />
in the parent component where child components should appear.
<Routes>
<Route path="dashboard" element={<DashboardLayout />}>
<Route path="home" element={<DashboardHome />} />
<Route path="settings" element={<DashboardSettings />} />
</Route>
</Routes>
// Inside DashboardLayout.js
return (
<div>
<Sidebar />
<Outlet />
</div>
);
Q2: How can I redirect a user after login?
A: Use <Navigate to="/dashboard" replace />
to programmatically redirect.
if (userIsAuthenticated) {
return <Navigate to="/dashboard" replace />;
}
Q3: How to protect private routes?
A: Wrap the route in a custom component that checks authentication and redirects if necessary.
function PrivateRoute({ children }) {
return isLoggedIn ? children : <Navigate to="/login" />;
}
<Route path="/admin" element={<PrivateRoute><AdminPage /></PrivateRoute>} />
Q4: How can I get route parameters?
A: Use the useParams()
hook.
// URL: /user/123
import { useParams } from 'react-router-dom';
function User() {
const { id } = useParams();
return <h2>User ID: {id}</h2>;
}
Q5: How to navigate programmatically from code?
A: Use the useNavigate()
hook.
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
const handleClick = () => {
navigate('/profile');
};
✅ Best Practices
- Use meaningful paths (e.g.,
/products/:id
). - Use
<Link />
instead of anchor tags to avoid full page reloads. - Use
<Outlet />
for nested route rendering. - Use layout routes to wrap pages in shared UI (like sidebar/header).
🌍 Real-World Scenario
In a dashboard app, different sections like “Home,” “Reports,” and “User Settings” are separate routes. React Router v6 helps structure this with clean nested routes and layout wrappers.
Nested & Dynamic Routes
🧠 Detailed Explanation
Nested Routes are routes inside other routes. You use them when one component needs to show another component inside it. For example, a "Dashboard" page might have subpages like "Profile" or "Settings" — these can be nested routes.
Dynamic Routes are routes with changing parts, like /user/:id
. The :id
is dynamic — it can be any value, such as /user/1
or /user/99
. This is useful when you want to show a page based on user or product info.
React Router helps you build both types easily using components like <Routes>
, <Route>
, <Outlet />
, and useParams()
.
- ✅ Use
<Outlet />
to render nested child components - ✅ Use
useParams()
to get dynamic route values
Simple Analogy: Think of nested routes like a house with rooms inside it (e.g., Dashboard → Profile room). Dynamic routes are like naming the room depending on the guest (/user/1
, /user/2
).
🛠️ Implementation
Step 1: Setup Routes in App.js
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Dashboard from "./pages/Dashboard";
import Profile from "./pages/Profile";
import Settings from "./pages/Settings";
import User from "./pages/User";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={ } >
<Route path="profile" element={ } />
<Route path="settings" element={ } />
</Route>
<Route path="/user/:id" element={ } />
</Routes>
</BrowserRouter>
);
}
Step 2: Add <Outlet />
to the Dashboard layout
// Dashboard.js
import { Outlet } from "react-router-dom";
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
<Outlet /> {/* Nested Routes Render Here */}
</div>
);
}
Step 3: Access dynamic route data using useParams()
// User.js
import { useParams } from "react-router-dom";
function User() {
const { id } = useParams();
return (
<div>
<h3>User ID: {id}</h3>
</div>
);
}
✅ Now, when you go to /dashboard/profile
, it shows the Profile page inside the Dashboard.
✅ When you go to /user/42
, it shows dynamic content for user 42.
💡 Examples
Example 1: Nested Route in React Router v6
import { Routes, Route, Outlet } from "react-router-dom";
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Outlet />
</div>
);
}
function App() {
return (
<Routes>
<Route path="dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
Example 2: Dynamic Route
<Route path="/user/:id" element={<UserProfile />} />
// Inside UserProfile
const { id } = useParams();
🔁 Alternatives
- Use layout components manually instead of
<Outlet />
- For non-SPA apps, you can use server-rendered logic with query params instead
❓ General Q&A
Q: Why should I use nested routes?
A: To keep your components organized and layouts DRY. Instead of repeating navbars or wrappers, you place them once and nest content inside.
Q: When is dynamic routing most useful?
A: When you want to load different content based on URL params (e.g. viewing /user/5 vs /user/6).
🛠️ Technical Q&A
Q: What is <Outlet />
in React Router v6?
A: It’s a placeholder for child routes in nested routing. Wherever you place <Outlet />
, the child component gets rendered.
Q: How do you get dynamic route params?
import { useParams } from "react-router-dom";
const { id } = useParams();
✅ Best Practices
- Use nested routes for shared layouts.
- Don’t hardcode URLs – use route params and navigation helpers.
- Validate dynamic parameters (e.g. check if
id
exists).
🌍 Real-World Scenario
In an admin panel, the sidebar stays fixed and the nested content switches between "Dashboard", "Users", and "Settings". You achieve this with nested routes and an <Outlet />
.
Route Guards & Redirects
🧠 Detailed Explanation
Route Guards help protect some pages in your app. For example, if a user is not logged in, they should not see the dashboard page. A route guard will check this and stop them.
Redirects send users to another page if something is not allowed. For example, if the user tries to open /dashboard
without logging in, we redirect them to /login
.
This is very useful for apps with private content — like admin panels, dashboards, or any page that should only be visible to logged-in users.
React Router (the library we use for routing in React) lets us build this using a component. We check if the user is logged in (maybe by checking a token). If yes, we show the page. If not, we redirect.
🔐 Simple Example: If someone is not logged in, don’t show the "Profile" page. Instead, move them to the "Login" page automatically.
🚪 Think of it like a security guard at a club door. If your name is not on the list (not logged in), you can’t enter (view the page). Instead, you are sent home (redirected).
🛠️ Implementation
// RequireAuth.js
import { Navigate, useLocation } from 'react-router-dom';
export function RequireAuth({ children }) {
const location = useLocation();
const isAuthenticated = localStorage.getItem("token");
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// App.js
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={
<RequireAuth>
<Dashboard />
</RequireAuth>
} />
</Routes>
💡 Examples
Example 1: Redirecting Users Who Are Not Logged In
Let’s say we have a dashboard page that only logged-in users can see. If someone who is not logged in tries to visit /dashboard
, we’ll send them to the /login
page.
We do this using a RequireAuth
component:
// RequireAuth.js
import { Navigate, useLocation } from 'react-router-dom';
export function RequireAuth({ children }) {
const location = useLocation();
const isLoggedIn = localStorage.getItem('token'); // Check if token exists
if (!isLoggedIn) {
// Redirect to login and save the current page
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children; // If logged in, show the protected page
}
Now we wrap our protected route with this:
// App.js
import { Routes, Route } from 'react-router-dom';
import Dashboard from './Dashboard';
import Login from './Login';
import { RequireAuth } from './RequireAuth';
function App() {
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={
<RequireAuth>
<Dashboard />
</RequireAuth>
} />
</Routes>
);
}
✅ Now, only logged-in users with a token can see the Dashboard. Others will be sent to the Login page.
Example 2: Redirect Logged-In Users Away from Login Page
Let’s say we don’t want already logged-in users to visit the login page again. We can redirect them to the dashboard.
// Login.js
import { Navigate } from 'react-router-dom';
function Login() {
const isLoggedIn = localStorage.getItem('token');
if (isLoggedIn) {
return <Navigate to="/dashboard" />; // Already logged in → go to dashboard
}
return (
<div>
<h2>Login Form</h2>
{/* Actual login form goes here */}
</div>
);
}
✅ This keeps the user experience clean and prevents confusion.
Example 3: After Login, Go Back to the Page the User Tried to Access
Sometimes, users try to open a private page like /settings
before logging in. After login, we can send them back there.
This uses useLocation()
and useNavigate()
:
// Login.js
import { useLocation, useNavigate } from 'react-router-dom';
function Login() {
const location = useLocation();
const navigate = useNavigate();
const from = location.state?.from?.pathname || "/dashboard";
const handleLogin = () => {
// Assume login success
localStorage.setItem("token", "yes");
navigate(from, { replace: true }); // Go back to original page
};
return (
<button onClick={handleLogin}>Login</button>
);
}
✅ This helps users continue where they left off.
🔁 Alternatives
- Use Redux or Context API to manage auth state globally.
- Use server-side rendering frameworks (e.g., Next.js) for better control over auth at the page level.
❓ General Q&A
Q: What is a route guard in React?
A: A route guard is a way to stop users from visiting certain pages unless they meet specific conditions. For example, you can use a route guard to block users from opening the dashboard if they are not logged in. It acts like a door lock — you only enter if you have the key (like a token).
Q: Why do we need route guards in web apps?
A: We use route guards to protect sensitive or private pages. In real apps like admin dashboards, user profiles, and payment pages, you don’t want just anyone to access them. Route guards ensure that only the right users (like logged-in users) can see those pages.
Q: What is a redirect?
A: A redirect sends the user from one page to another automatically. For example, if someone tries to go to /dashboard
without logging in, we "redirect" them to /login
so they can log in first. It’s like saying: “You can’t go there — go here instead.”
Q: How do I redirect a user in React?
A: In React Router v6, you use the <Navigate />
component. It acts like a shortcut to another page. You can also use the useNavigate()
hook to redirect users after they perform an action (like clicking a login button).
Q: Can I use route guards for roles too (like admin vs normal user)?
A: Yes! Route guards are not just for checking if a user is logged in. You can also check if the user is an admin, a manager, or has any specific permission. Based on that, you can show or hide pages like /admin
or /settings
.
Q: What happens if a user deletes their token in localStorage?
A: If your route guard checks for a token and it’s missing, the user will be blocked and redirected (usually to /login
). That’s exactly what should happen to protect your app’s content.
Q: Can I show a loading screen while checking the user status?
A: Yes, you can add a small spinner or "Loading..." text while your app checks if the user is logged in (especially when checking from an API or context). This makes your app look smoother and avoids flickering.
🛠️ Technical Q&A
Q1: How do I create a reusable route guard in React Router v6?
A: Create a wrapper component (e.g., RequireAuth
) that checks for a login condition, and redirects if the user is not authenticated.
✅ Example:
// RequireAuth.js
import { Navigate, useLocation } from 'react-router-dom';
export function RequireAuth({ children }) {
const location = useLocation();
const isAuthenticated = localStorage.getItem("token");
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
🔐 Use this component around any route you want to protect.
// App.js
<Route path="/dashboard" element={
<RequireAuth>
<Dashboard />
</RequireAuth>
} />
Q2: How can I redirect a user after login to the page they originally wanted?
A: Save the page they tried to access using useLocation()
and return to that after successful login using useNavigate()
.
✅ Example:
// Login.js
import { useLocation, useNavigate } from 'react-router-dom';
function Login() {
const location = useLocation();
const navigate = useNavigate();
const from = location.state?.from?.pathname || "/dashboard";
const handleLogin = () => {
localStorage.setItem("token", "sample-token");
navigate(from, { replace: true }); // Redirect to original page
};
return (
<button onClick={handleLogin}>Login</button>
);
}
📌 This helps maintain a smooth experience for users.
Q3: How do I prevent logged-in users from visiting the login or signup page?
A: Inside your Login
or Signup
components, check if the user is already authenticated. If they are, redirect them to a default page.
✅ Example:
// Login.js
import { Navigate } from 'react-router-dom';
function Login() {
const isLoggedIn = localStorage.getItem('token');
if (isLoggedIn) {
return <Navigate to="/dashboard" replace />;
}
return (
<div>
<h2>Login Form</h2>
{/* ...form here... */}
</div>
);
}
Q4: How can I apply route guards to multiple routes without repeating code?
A: You can create a layout route or a wrapper around multiple child routes.
✅ Example:
<Route element={<RequireAuth />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
📦 This ensures all nested routes are protected by RequireAuth
.
Q5: How do I pass route state (like a message or reason) during redirect?
A: You can pass a `state` object inside the `Navigate` component or the `useNavigate()` function.
✅ Example:
return <Navigate to="/login" state={{ reason: "not-authenticated" }} replace />;
Then inside the login component:
const location = useLocation();
const reason = location.state?.reason;
if (reason === "not-authenticated") {
alert("Please log in to continue");
}
✅ Best Practices
-
✅ 1. Always centralize your route guard logic
Create a reusable component likeRequireAuth
instead of repeating the login-check logic in every component.// ✅ Good Practice <Route path="/dashboard" element={ <RequireAuth> <Dashboard /> </RequireAuth> } />
❌ Bad: Checking login in every page manually with
if (!token)
-
✅ 2. Store minimal and secure auth data
Save only what’s needed (like a token or user ID) inlocalStorage
orsessionStorage
. Avoid storing full user objects or sensitive data.Example:
// ✅ Good localStorage.setItem("token", response.token); // ❌ Bad (storing all user data) localStorage.setItem("user", JSON.stringify(response.user));
-
✅ 3. Use
<Navigate />
for clean, declarative redirects
Instead of manually pushing with history, use React Router’s<Navigate />
for better control and re-renders.// ✅ Good if (!isAuthenticated) { return <Navigate to="/login" replace />; }
❌ Bad: Using deprecated or hacky methods to redirect.
-
✅ 4. Redirect users back to their original destination after login
UseuseLocation()
to track the page they wanted before being redirected to login.Example:
const from = location.state?.from?.pathname || "/dashboard"; navigate(from); // After successful login
-
✅ 5. Guard role-based routes with custom logic
If your app has roles (like "admin", "user"), create a wrapper that checks the user role before rendering.Example:
function RequireAdmin({ children }) { const userRole = localStorage.getItem("role"); return userRole === "admin" ? children : <Navigate to="/unauthorized" />; }
-
✅ 6. Show a loading state while checking auth from APIs
If you're validating tokens with a backend, show a loader before rendering routes.// Example inside RequireAuth if (loading) return <div>Loading...</div>;
-
✅ 7. Don’t expose protected routes in the navbar for unauthenticated users
Hide dashboard or profile links if the user isn’t logged in.{isLoggedIn && <NavLink to="/dashboard">Dashboard</NavLink>}
🌍 Real-World Scenarios
-
1. E-Commerce Dashboard (Admin Only)
In an e-commerce app, only admins should be able to access/admin
routes like order management or user management.
✅ Use<RequireAdmin>
wrapper to check role before showing the page. -
2. Job Portal — Only Logged-in Users Can Apply
On a job portal like LinkedIn, users must be logged in to apply for a job.
✅ Use<RequireAuth>
to guard the/apply/:jobId
route and redirect to/login
if unauthenticated. -
3. Banking App — Redirect to Login on Expired Token
If the user's session token expires, they are automatically redirected to/login
.
✅ Detect token expiry via an API and usenavigate('/login')
inside an interceptor or context. -
4. SaaS App — Prevent Logged-In Users from Seeing Signup
In tools like Notion or Slack, logged-in users shouldn’t access/signup
or/login
pages.
✅ InLogin.js
, check for token and redirect to/dashboard
if already authenticated. -
5. After Login, Return to Protected Page
If a user tries to go to/settings
and is redirected to/login
, after successful login, they are sent back to/settings
.
✅ UseuseLocation().state?.from
andnavigate(from)
. -
6. Subscription App — Guard Premium Pages
In a platform like Medium or Netflix, only paid users can view certain content (e.g.,/premium-article
or/watch/series
).
✅ Add a route guard that checks subscription status before allowing access. -
7. Multi-Tenant Dashboard — Redirect Based on Organization
In tools like Jira or Salesforce, users belong to different companies. After login, redirect each user to their organization-specific route (e.g.,/org-xyz/dashboard
).
✅ Fetch org info after login and usenavigate(`/org-${orgId}/dashboard`)
.
Layout Routes
🧠 Detailed Explanation
Layout Routes are special routes in React that help you keep things clean and consistent across your app.
Imagine you have a website with a navbar at the top and a footer at the bottom. You want both of them to stay visible no matter which page the user visits (like Dashboard, Settings, or Profile).
Instead of writing the same navbar and footer code again and again in every page component, you can create a layout component that has the shared design. Then, use <Outlet />
to show different pages in the middle of that layout.
So, every time the user switches a page, only the middle content changes — the navbar and footer stay the same.
✅ Simple Example: Think of a sandwich: the top bun is your Navbar, the bottom bun is your Footer, and the filling in the middle changes based on the route. That middle part is where <Outlet />
goes!
✅ This is a great way to build apps like dashboards, admin panels, or any app with a consistent layout.
💡 Examples
Example 1: Creating a Layout Route with Navbar and Footer
Let’s say your app has a Navbar
and a Footer
that should be visible on all main pages like /dashboard
and /settings
. You don’t want to copy that code into every page.
Step 1: Create a Layout Component
// MainLayout.js
import { Outlet } from "react-router-dom";
import Navbar from "./Navbar";
import Footer from "./Footer";
function MainLayout() {
return (
<>
<Navbar />
<main>
<Outlet /> {/* This is where child pages will be shown */}
</main>
<Footer />
</>
);
}
export default MainLayout;
✅ This layout wraps any page that needs the same header and footer.
Step 2: Setup Your Routes with the Layout
// App.js
import { BrowserRouter, Routes, Route } from "react-router-dom";
import MainLayout from "./MainLayout";
import Dashboard from "./pages/Dashboard";
import Settings from "./pages/Settings";
import Login from "./pages/Login";
function App() {
return (
<BrowserRouter>
<Routes>
{/* Protected layout */}
<Route element={<MainLayout />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
{/* Public route */}
<Route path="/login" element={<Login />} />
</Routes>
</BrowserRouter>
);
}
✅ This means: whenever someone goes to /dashboard
or /settings
, React will first load MainLayout
, and then show the requested page inside the layout.
Example 2: Using Multiple Layouts for Public and Private Pages
Sometimes, the /login
or /register
pages shouldn’t have the dashboard layout. You can make a separate layout for public pages too.
// PublicLayout.js
import { Outlet } from "react-router-dom";
function PublicLayout() {
return (
<div className="public-wrapper">
<Outlet />
</div>
);
}
// App.js (updated)
<Routes>
<Route element={<MainLayout />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
<Route element={<PublicLayout />}>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Route>
</Routes>
✅ This keeps your UI clean and shows the right layout for each section of the app.
🔁 Alternative Concepts
- Using
React Context
to inject layouts globally - Conditional rendering layout in each component (not recommended)
❓ General Q&A
Q: What is a layout route in React?
A: A layout route is a special kind of route that wraps multiple child routes with a shared layout — like a header, sidebar, or footer. It lets you display common parts of the page on every route without repeating the code.
For example, you might want a dashboard to have the same navbar and sidebar on every page. Instead of adding those in every component, you wrap them in a layout and render the current page inside using <Outlet />
.
Q: What is the purpose of <Outlet />
in a layout route?
A: The <Outlet />
is like a placeholder. It tells React Router where to show the child component based on the current route. When the user visits a nested route, that component appears in the place of the <Outlet />
.
It works like: “Keep the navbar and footer where they are, and show the selected page here.”
Q: Why should I use layout routes instead of adding headers in every component?
A: Because it’s cleaner and more maintainable. If you need to change the layout (e.g., update the navbar), you only change it in one place. Otherwise, you would have to update every page where you manually added the layout code — which is hard to manage and easy to break.
Q: Can I use multiple layout routes in the same app?
A: Yes! You can create separate layout routes for different parts of your app. For example:
- 🔐 A private layout for logged-in users (dashboard)
- 🔓 A public layout for login, register, and landing pages
This makes it easy to show or hide certain layout elements based on the page type.
Q: Can I use layout routes with route guards?
A: Absolutely. You can wrap a layout route (or its children) with a <RequireAuth />
component. This way, only authenticated users can access routes inside a layout like /dashboard
.
✅ Example:
<Route element={<RequireAuth><MainLayout /></RequireAuth>}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
Q: Do layout routes work with dynamic or nested routes?
A: Yes! Layout routes are perfect for nested routing. You can have dynamic child routes (like /users/:id
) inside a layout, and they will be rendered properly inside the <Outlet />
.
🛠️ Technical Q&A
Q1: How do I create a layout route in React Router v6?
A: You create a layout component that includes shared elements like navbar or footer, and place <Outlet />
where child routes should render. Then, you wrap child routes inside it.
✅ Example:
// MainLayout.js
import { Outlet } from 'react-router-dom';
function MainLayout() {
return (
<>
<header>Navbar</header>
<Outlet /> {/* This is where the current page renders */}
<footer>Footer</footer>
</>
);
}
// App.js
<Routes>
<Route element={<MainLayout />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
</Routes>
Q2: What does <Outlet />
do in a layout route?
A: <Outlet />
is a placeholder that tells React Router where to render the matching child route.
function Layout() {
return (
<div>
<Sidebar />
<main><Outlet /></main>
</div>
);
}
When the user visits /dashboard
, the Dashboard
component will appear in place of <Outlet />
.
Q3: Can I use nested layout routes?
A: Yes. You can nest layouts inside layouts to handle complex UI, such as a main layout and a sub-layout for profile pages.
// App.js
<Route element={<MainLayout />}>
<Route element={<ProfileLayout />}>
<Route path="/profile/view" element={<ViewProfile />} />
<Route path="/profile/edit" element={<EditProfile />} />
</Route>
</Route>
Q4: How do I add route guards inside layout routes?
A: You can wrap the layout or the child routes with a component like RequireAuth
that checks if the user is authenticated.
// App.js
<Route element={<RequireAuth><MainLayout /></RequireAuth>}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
✅ Only authenticated users will see the dashboard inside the layout.
Q5: How can I handle multiple layouts for public and private pages?
A: Define two different layout components — one for public pages like login, and another for authenticated areas like dashboard.
// PublicLayout.js
function PublicLayout() {
return (
<div className="public">
<Outlet />
</div>
);
}
// App.js
<Routes>
<Route element={<PublicLayout />}>
<Route path="/login" element={<Login />} />
</Route>
<Route element={<MainLayout />}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
</Routes>
✅ Best Practices
✅ 1. Use layout routes to avoid repeating code (like navbars/footers)
Don’t put your header/footer in every page manually. Wrap pages inside a layout route.
// MainLayout.js
function MainLayout() {
return (
<>
<Navbar />
<Outlet />
<Footer />
</>
);
}
// App.js
<Route element={<MainLayout />}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
✅ You update layout only once — it affects all pages using it.
✅ 2. Use different layouts for different user areas
Create separate layout components for public pages (like login) and private areas (like dashboard).
// PublicLayout.js
function PublicLayout() {
return <Outlet />;
}
// App.js
<Route element={<PublicLayout />}>
<Route path="/login" element={<Login />} />
</Route>
<Route element={<MainLayout />}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
✅ Keeps your UI clean and avoids layout conflicts.
✅ 3. Keep layout components presentational (no business logic)
Layout components should handle structure, not logic. Fetching data or handling state should stay in child pages or context.
// ✅ Good
function Layout() {
return (
<>
<Sidebar />
<Outlet />
</>
);
}
❌ Bad: Calling APIs or updating state inside the layout component.
✅ 4. Wrap layouts with authentication guards when needed
If a layout is for protected pages, wrap it with a route guard like <RequireAuth>
.
// App.js
<Route element={<RequireAuth><MainLayout /></RequireAuth>}>
<Route path="/dashboard" element={<Dashboard />} />
</Route>
✅ Prevents unauthenticated users from accessing private layouts.
✅ 5. Avoid deeply nesting too many layouts
Keep layout nesting flat and meaningful. Don’t create unnecessary layouts for every page level.
// ✅ Good: Dashboard layout and sub-routes
<Route element={<DashboardLayout />}>
<Route path="settings" element={<Settings />} />
<Route path="profile" element={<Profile />} />
</Route>
❌ Bad: Creating a new layout for each individual page.
✅ 6. Show a fallback (like loading or 404) inside layouts
It’s a good idea to add a fallback view (like a spinner or error page) inside the layout in case nothing matches or while loading content.
<Route path="*" element={<NotFound />} />
🌍 Real-World Examples
-
1. Admin Dashboard
Apps like Shopify Admin or Notion have a consistent sidebar, navbar, and user menu on every internal page.
✅ Use aMainLayout
with sidebar + navbar + <Outlet /> to wrap routes like/dashboard
,/orders
,/settings
. -
2. Authentication Pages
On apps like Twitter or Facebook, login and signup pages have a different layout — no navbar or dashboard.
✅ Use aPublicLayout
for/login
,/register
routes — no header/footer needed. -
3. E-commerce Website
A site like Amazon shows the same top bar and footer on all product/category pages.
✅ Create aStoreLayout
with<Outlet />
to wrap routes like/products
,/cart
,/checkout
. -
4. Blogging Platform
In platforms like Medium, the homepage layout is different from the article reading layout.
✅ Use aReaderLayout
for/posts/:slug
andMainLayout
for homepage/dashboard. -
5. Role-Based Layouts (Admin vs User)
In internal apps like Jira or Salesforce, admins and regular users see different navbars or sidebars.
✅ Conditionally renderAdminLayout
orUserLayout
based on the role. -
6. SaaS Platform with Onboarding Flow
Tools like Slack or Asana show a special layout during onboarding (welcome steps, team setup).
✅ Use anOnboardingLayout
for/onboarding/*
steps. -
7. Multi-Tenant Web App
In apps like Basecamp or Notion Teams, each organization has its own dashboard and subdomain.
✅ Use aTenantLayout
that loads org-specific data and wraps all/[org]/dashboard
routes.
useState Forms
🧠 Detailed Explanation
useState is a tool in React that helps your app remember things — like what a user types in a form.
When you build a form (like login, signup, or contact), you usually have input fields like “name,” “email,” or “password.” With useState
, you can store what the user types and use it later — like when they click “Submit.”
Without useState: The form just sits there. You’d need to grab the values manually.
With useState: The form is connected to your app’s memory. It updates automatically as the user types.
✅ Example: When a user types their name into a box, useState
stores it in a variable. When they click “Submit,” your app already knows what they typed. You don’t have to “look” for it — it’s ready to use.
Why it’s helpful:
- You can show instant feedback (like errors or success messages)
- You can validate the data before sending it
- You can reset the form after submission
- It makes your form interactive and dynamic
In short: useState
is like a notepad for your form — it keeps track of what the user types, and lets you use it anytime in the app.
🛠️ Best Implementation
This example shows how to create a clean and reusable login form using useState
in React.
✅ You’ll learn how to:
- Manage form inputs using
useState
- Handle form submission
- Use controlled components
- Perform basic validation
Step 1: Create your component
import { useState } from 'react';
function LoginForm() {
// Step 2: useState to store form data
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
// Step 3: Submit function
const handleSubmit = (e) => {
e.preventDefault(); // Prevent page reload
if (!email || !password) {
setError('Both fields are required');
return;
}
// Step 4: Send data (simulate login)
alert(\`Email: \${email}\nPassword: \${password}\`);
setError('');
setEmail('');
setPassword('');
};
return (
<form onSubmit={handleSubmit} style={{ maxWidth: 400 }}>
<h3>Login Form</h3>
{error && <p style={{ color: 'red' }}>{error}</p>}
<div>
<label>Email:</label><br />
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
required
/>
</div>
<div style={{ marginTop: '1rem' }}>
<label>Password:</label><br />
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter password"
required
/>
</div>
<button type="submit" style={{ marginTop: '1rem' }}>
Login
</button>
</form>
);
}
🧠 Explanation:
useState('')
is used to store each input’s value.onChange
updates the state whenever the user types.handleSubmit()
prevents page reload and checks input.alert()
simulates a real login — in a real app, you would send the data to an API.- Inputs are "controlled" — React controls what they display.
✅ Benefits of This Implementation:
- Easy to extend (add new fields like username, phone, etc.)
- Simple validation logic built-in
- Clean user experience
- Works well with APIs and real authentication systems
💡 Examples
Example 1: A Basic Login Form Using useState
This example will help you understand how to create a login form where the user's email and password are stored using useState
.
import { useState } from 'react';
function LoginForm() {
// Step 1: Define two pieces of state: email and password
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// Step 2: Handle form submission
const handleSubmit = (e) => {
e.preventDefault(); // stop the page from refreshing
// Step 3: Show the entered data (in a real app, you'd send this to an API)
alert(\`Email: \${email}\nPassword: \${password}\`);
// Step 4: Clear the form
setEmail('');
setPassword('');
};
return (
<form onSubmit={handleSubmit}>
<h3>Login</h3>
<label>Email:</label><br />
<input
type="email"
value={email} // Step 5: Controlled input
onChange={(e) => setEmail(e.target.value)} // Step 6: Update state
placeholder="Enter your email"
/>
<br /><br />
<label>Password:</label><br />
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
/>
<br /><br />
<button type="submit">Login</button>
</form>
);
}
What this code does:
- 💾 Stores the user's email and password using
useState
- 🎯 Tracks input changes with
onChange
and updates the state - 🚀 Prevents the page from refreshing on form submit
- 📨 Displays the input using
alert
(or send to server in real apps)
Example 2: useState with a Single Object
This shows how to manage multiple inputs with just one useState
instead of one for each field.
import { useState } from 'react';
function SignupForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
password: ''
});
const handleChange = (e) => {
setFormData({
...formData, // keep the other fields
[e.target.name]: e.target.value, // update only the current field
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData); // or send to backend
};
return (
<form onSubmit={handleSubmit}>
<input
name="name"
placeholder="Name"
value={formData.name}
onChange={handleChange}
/>
<input
name="email"
placeholder="Email"
value={formData.email}
onChange={handleChange}
/>
<input
name="password"
placeholder="Password"
type="password"
value={formData.password}
onChange={handleChange}
/>
<button type="submit">Sign Up</button>
</form>
);
}
Why this is useful:
- ✅ All form values are stored in one object
- ✅ Easier to manage and scale as the form grows
- ✅ Cleaner code when dealing with many fields
🔁 Alternative Concepts
- Using
useReducer
for complex forms - Using libraries like
react-hook-form
orFormik
for validation & state
❓ General Q&A
Q: What is useState used for in forms?
A: In React, useState
helps us store and manage the data that the user types into a form. For example, when someone types their name or email, we use useState
to remember that value inside the component.
Instead of letting the browser manage the input directly, we control the input using React. This is called a controlled component. It gives us more power — like showing live validation, disabling the button until fields are filled, or clearing the form after submission.
Q: What does “controlled input” mean in React?
A: A controlled input is one where the value of the input field is linked directly to React state using useState
. The input shows what’s in the state, and any changes update the state.
It’s like saying: “I’m not letting the browser control this input. I’m letting React handle it.”
const [name, setName] = useState('');
<input value={name} onChange={(e) => setName(e.target.value)} />
✅ Now, the input will always match the value in name
.
Q: Can I use multiple useState hooks for different form fields?
A: Yes! You can use one useState
for each field, like name
, email
, and password
. This is useful when you want to handle each field separately and clearly.
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
Q: Can I store all form values in one useState?
A: Absolutely. If you prefer, you can store all your form fields in one object using a single useState
hook. This is useful for larger forms and helps reduce repetitive code.
const [form, setForm] = useState({ name: '', email: '' });
Then update using:
onChange={(e) => setForm({ ...form, [e.target.name]: e.target.value })}
Q: What happens if I don’t use useState in forms?
A: If you don’t use useState
, your form is called an “uncontrolled form.” That means React doesn’t know or manage the values. You would need to grab them manually using document.querySelector
or refs
.
That’s fine for some simple forms, but using useState
makes everything smoother and more interactive — like showing error messages, disabling buttons, or changing the UI based on what’s typed.
Q: How do I clear form inputs after the user submits?
A: Just reset your state values back to empty strings after form submission:
setName('');
setEmail('');
setPassword('');
This will instantly clear the inputs because their values are tied to the state.
🛠️ Technical Q&A
Q1: How do I create a controlled input using useState?
A: A controlled input uses React state to track what’s typed. You bind value
to state and update it using onChange
.
import { useState } from 'react';
function NameForm() {
const [name, setName] = useState('');
return (
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Your name"
/>
);
}
✅ The input always reflects what’s in state. This is key to validation and reactivity in React forms.
Q2: How do I manage multiple inputs with one useState?
A: Store all inputs in an object and update fields by key.
const [form, setForm] = useState({ email: '', password: '' });
const handleChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value
});
};
Input Example:
<input
name="email"
value={form.email}
onChange={handleChange}
/>
✅ This keeps code DRY and makes it easy to handle big forms.
Q3: How do I validate inputs before submitting?
A: Add validation inside your handleSubmit
function before processing or sending data.
const handleSubmit = (e) => {
e.preventDefault();
if (!form.email || !form.password) {
alert('All fields are required');
return;
}
// Process login
console.log(form);
};
✅ You can also use regex for email/password checks if needed.
Q4: How do I reset a form after submission?
A: Just set state values back to empty strings.
setForm({ email: '', password: '' });
✅ This clears all the fields instantly because their values are tied to the state.
Q5: What’s the best way to reuse input logic in multiple forms?
A: Create a reusable input component that accepts name
, value
, and onChange
.
// InputField.js
function InputField({ name, type = 'text', value, onChange, label }) {
return (
<div>
<label>{label}</label>
<input
name={name}
type={type}
value={value}
onChange={onChange}
/>
</div>
);
}
// Usage
<InputField
name="email"
label="Email"
value={form.email}
onChange={handleChange}
/>
✅ Great for large apps where you use inputs in many places.
✅ Best Practices
✅ 1. Use controlled components
Always bind your input value to React state. This makes the input predictable and allows for validation, UI control, and resetting.
const [email, setEmail] = useState('');
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
❌ Don’t use uncontrolled inputs (i.e., without value
).
✅ 2. Use a single state object for larger forms
When your form has many fields, use a single object to manage all inputs — it keeps your code short and clean.
const [form, setForm] = useState({
name: '',
email: '',
password: ''
});
const handleChange = (e) => {
setForm({ ...form, [e.target.name]: e.target.value });
};
✅ Works well for signup, checkout, or profile forms.
✅ 3. Validate before submit
Don’t allow form submission if required fields are empty or invalid.
const handleSubmit = (e) => {
e.preventDefault();
if (!form.email.includes('@')) {
alert('Invalid email');
return;
}
// send form
};
✅ Keeps your data clean and prevents bad submissions.
✅ 4. Clear the form after successful submission
Reset your input values by clearing the state — gives a better user experience.
setForm({ name: '', email: '', password: '' });
✅ Helps users know their data was submitted successfully.
✅ 5. Group related logic using custom hooks (for advanced use)
If your form gets complex, extract the logic into a reusable custom hook.
// useForm.js
function useForm(initialState) {
const [form, setForm] = useState(initialState);
const handleChange = (e) => {
setForm({ ...form, [e.target.name]: e.target.value });
};
return [form, handleChange, setForm];
}
// In component
const [form, handleChange, resetForm] = useForm({ email: '', password: '' });
✅ Useful in large apps where multiple forms share logic.
✅ 6. Reuse input fields with components
Make a reusable input field to keep your form UI clean and consistent.
function InputField({ label, name, value, onChange, type = "text" }) {
return (
<div>
<label>{label}</label>
<input
type={type}
name={name}
value={value}
onChange={onChange}
/>
</div>
);
}
🌍 Real-World Scenarios
-
1. Login Form
Every app with accounts — like Facebook, Slack, or Notion — needs a login form.
✅ UseuseState
to manageemail
andpassword
values, and submit them to an authentication API. -
2. Signup Form
Sites like LinkedIn or Gmail use multi-field forms for account creation.
✅ UseuseState
to manage multiple fields: name, email, password, confirm password — possibly using a single object state. -
3. Contact/Feedback Form
Common on e-commerce and business websites like Shopify or Airbnb.
✅ UseuseState
to capturename
,email
, andmessage
and send it via API or email. -
4. Newsletter Signup (Email Only)
Found at the bottom of blogs like Medium or newsletters like Substack.
✅ UseuseState
for the email input and handle basic validation (like email format check). -
5. Search Box with Suggestions
Used in Google Search, YouTube, or Amazon product search.
✅ UseuseState
to track what the user is typing and update search suggestions in real time. -
6. Multi-Step Form (Checkout or Survey)
Sites like Amazon and Shopify use multi-step forms for address, payment, and confirmation.
✅ UseuseState
to track current step and store data for each step in a combined state object. -
7. Toggle Between Login and Signup
Apps like Spotify or Zoom allow users to switch between login/signup on the same page.
✅ UseuseState
to track which form is shown, and separate states for each form’s data.
React Hook Form
🧠 Detailed Explanation
react-hook-form is a tool that makes it easier to build forms in React.
Usually, when you build a form in React, you use useState()
to save the value of each input field (like name, email, password). But if your form has many fields, your code gets long and messy fast.
react-hook-form solves that by letting you:
- 📌 Connect form inputs using a simple
register()
function - ✅ Handle validation (like required fields, email format) easily
- 🚀 Submit forms without writing a lot of
useState()
code
It works by using **refs** instead of state, which means it doesn’t re-render the whole component when the user types something. This makes it super fast and lightweight.
🎯 How it works:
- You use
useForm()
to create a form manager - You use
register()
to connect each input - You use
handleSubmit()
to handle form submission - You check for errors with
formState.errors
✅ Example:
const { register, handleSubmit, formState: { errors } } = useForm();
<input {...register("email", { required: "Email is required" })} />
{errors.email && <p>Email is required</p>}
In short: react-hook-form
helps you build forms faster, with fewer bugs, and much less code than using only useState()
.
🛠️ Best Implementation
Let’s build a professional and clean login form using react-hook-form
with validation, error messages, and a successful form submission handler.
✅ This implementation follows best practices:
- Clean JSX using
register()
- Field-level validation (required, minLength)
- Readable error messages using
formState.errors
- Handles form submission with
handleSubmit()
Full Working Example:
import React from 'react';
import { useForm } from 'react-hook-form';
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors },
reset
} = useForm();
const onSubmit = (data) => {
console.log('Form submitted:', data);
alert(\`Logged in as \${data.email}\`);
reset(); // Clear the form after success
};
return (
<form onSubmit={handleSubmit(onSubmit)} style={{ maxWidth: '400px' }}>
<h3>Login Form</h3>
<div>
<label>Email:</label><br />
<input
{...register("email", {
required: "Email is required",
pattern: {
value: /^\S+@\S+$/i,
message: "Invalid email format"
}
})}
placeholder="Enter your email"
/>
{errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
</div>
<div style={{ marginTop: '1rem' }}>
<label>Password:</label><br />
<input
type="password"
{...register("password", {
required: "Password is required",
minLength: {
value: 6,
message: "Password must be at least 6 characters"
}
})}
placeholder="Enter password"
/>
{errors.password && <p style={{ color: 'red' }}>{errors.password.message}</p>}
</div>
<button type="submit" style={{ marginTop: '1rem' }}>
Login
</button>
</form>
);
}
📘 Step-by-Step Explanation:
useForm()
initializes the form and gives you useful functions likeregister
,handleSubmit
,reset
, anderrors
.register("email", { required: ..., pattern: ... })
connects the input to the form and applies validation rules directly.handleSubmit()
wraps youronSubmit
function and only runs it when all validations pass.formState.errors
contains error messages for each field that failed validation.reset()
clears the form after successful submission.
✅ Why This is the Best Practice:
- 🔍 Clean, readable code without cluttering each field with useState.
- ⚡ No re-renders on every keystroke — better performance.
- 📦 Handles validation, errors, and submission with just a few lines.
- ✅ Easy to add more fields or switch to schema validation using
yup
.
💡 Examples
Example 1: Simple Login Form
This is the most common use case — a login form with email
and password
fields. It uses react-hook-form
to manage input values and validations with much less code than useState
.
import React from 'react';
import { useForm } from 'react-hook-form';
function LoginForm() {
// Step 1: initialize react-hook-form
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
// Step 2: define your submit function
const onSubmit = (data) => {
console.log('Form Data:', data);
alert(\`Logged in as \${data.email}\`);
};
return (
<form onSubmit={handleSubmit(onSubmit)} style={{ maxWidth: 400 }}>
<h3>Login</h3>
<div>
<label>Email:</label><br />
<input
{...register('email', {
required: 'Email is required',
pattern: {
value: /^\S+@\S+$/i,
message: 'Enter a valid email'
}
})}
placeholder="you@example.com"
/>
{errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
</div>
<div style={{ marginTop: '1rem' }}>
<label>Password:</label><br />
<input
type="password"
{...register('password', {
required: 'Password is required',
minLength: {
value: 6,
message: 'Must be at least 6 characters'
}
})}
placeholder="******"
/>
{errors.password && <p style={{ color: 'red' }}>{errors.password.message}</p>}
</div>
<button type="submit" style={{ marginTop: '1rem' }}>Login</button>
</form>
);
}
---
### ✅ Step-by-Step Explanation
| Step | What Happens |
|------|--------------|
| 1️⃣ | `useForm()` initializes the form system |
| 2️⃣ | `register("email", { ... })` connects the input to the form and adds validation |
| 3️⃣ | `handleSubmit()` wraps your `onSubmit()` so it only runs if validations pass |
| 4️⃣ | `formState.errors` is used to show error messages below each field |
---
### 💡 Example 2: Signup Form with Multiple Fields (Single Submit)
```js
function SignupForm() {
const { register, handleSubmit, formState: { errors }, reset } = useForm();
const onSubmit = (data) => {
console.log(data);
alert(`Thanks for signing up, ${data.name}`);
reset(); // clears the form
};
return (
);
}
🔁 Alternative Concepts
- Using
useState
for each input (more manual) - Using
Formik
– another form library with Yup validation
❓ General Q&A
Q: What is react-hook-form?
A: react-hook-form
is a library in React that helps you build forms more easily and with less code. Instead of writing useState
for every field and handling each input manually, react-hook-form
does it all behind the scenes.
It’s faster, cleaner, and gives you built-in validation, error handling, and submission tools — all with a few lines of code.
Q: Why should I use react-hook-form instead of useState?
A: If you use useState
for every input, your code can get long and hard to manage — especially with many fields. It also causes your component to re-render every time the user types.
react-hook-form
avoids this by using input refs instead of state, which means your form works faster and with less re-rendering.
✅ Less code, ✅ better performance, ✅ built-in validation — that’s why it’s preferred in real-world apps.
Q: How do I connect an input field to react-hook-form?
A: You use the register()
function provided by useForm()
to connect each input. It looks like this:
<input {...register("email")} />
This line tells React Hook Form to track the value of the email field automatically.
Q: How do I show error messages?
A: You use formState.errors
to check if any field has failed validation. If it has, you show a message below the input.
{errors.email && <p>Email is required</p>}
Q: What happens when the user submits the form?
A: You wrap your submit function using handleSubmit()
. It first checks if all validations are correct. If they are, it calls your function with the form data. If not, it shows the error messages.
const onSubmit = (data) => {
console.log(data);
};
<form onSubmit={handleSubmit(onSubmit)}> ... </form>
Q: Can I reset the form after submission?
A: Yes! react-hook-form
gives you a reset()
function to clear the fields after the user submits the form.
reset(); // clears all input values
Q: Is react-hook-form used in real-world apps?
A: Absolutely! It’s used by thousands of developers and in production apps like dashboards, admin panels, SaaS products, CRMs, and e-commerce checkouts. It’s one of the most downloaded form libraries in the React ecosystem because it’s simple, fast, and flexible.
🛠️ Technical Q&A
Q1: How do I validate a required field in react-hook-form?
A: Use the register()
function with a required
rule. Then check formState.errors
to show a message if the user leaves it empty.
const { register, handleSubmit, formState: { errors } } = useForm();
<input
{...register("email", { required: "Email is required" })}
/>
{errors.email && <p>{errors.email.message}</p>}
✅ **This automatically shows the error when the field is empty.**
Q2: How do I validate an email format?
A: Use a regular expression in the pattern
rule inside register()
.
<input
{...register("email", {
required: "Email is required",
pattern: {
value: /^\S+@\S+\.\S+$/,
message: "Invalid email format"
}
})}
/>
✅ **Only valid emails like `hello@domain.com` will pass.**
Q3: How do I submit a form only when it is valid?
A: Wrap your onSubmit
handler inside handleSubmit()
. It prevents form submission if any field fails validation.
const onSubmit = (data) => {
console.log(data);
};
<form onSubmit={handleSubmit(onSubmit)}> ... </form>
✅ **You don't need to manually check for errors — it's automatic.**
Q4: How do I reset form fields after successful submission?
A: Use the reset()
function provided by useForm()
.
const { reset } = useForm();
const onSubmit = (data) => {
alert("Submitted!");
reset(); // Clears all fields
};
✅ **This is useful for contact forms, login, or signup forms.**
Q5: How do I show a custom error message per field?
A: Use the message
field inside each validation rule. Then read it from errors[field].message
.
<input
{...register("password", {
required: "Password is required",
minLength: {
value: 6,
message: "Must be at least 6 characters"
}
})}
/>
{errors.password && <p>{errors.password.message}</p>}
✅ **Displays the specific message based on what failed.**
Q6: Can I validate multiple rules at once (required + pattern + length)?
A: Yes, you can chain as many validation rules as needed in the register()
config.
<input
{...register("username", {
required: "Username is required",
minLength: { value: 3, message: "Too short" },
maxLength: { value: 20, message: "Too long" },
pattern: {
value: /^[a-zA-Z0-9_]+$/,
message: "Only letters, numbers, and underscores"
}
})}
/>
✅ **Each rule is checked in order and shows the appropriate message.**
Q7: How do I validate matching passwords (confirm password)?
A: Use the watch()
function to compare fields inside a custom validator.
const { register, watch } = useForm();
const password = watch("password");
<input type="password" {...register("password")} />
<input
type="password"
{...register("confirm", {
validate: (value) => value === password || "Passwords do not match"
})}
/>
✅ **Shows error if confirm password doesn't match original.**
✅ Best Practices
✅ 1. Use register() directly on inputs
Always bind your inputs using {...register('field')}
directly inside the tag — it's cleaner and more performant than extra wrappers.
<input
{...register("email", { required: "Email is required" })}
placeholder="Email"
/>
✅ Simple, declarative, and auto-tracked by react-hook-form.
✅ 2. Show custom error messages using formState.errors
Display helpful, human-readable error messages based on what went wrong.
{errors.email && <p style={{ color: "red" }}>{errors.email.message}</p>}
✅ Better UX and quick debugging during development.
✅ 3. Wrap onSubmit
with handleSubmit()
Never manually check validation — let handleSubmit()
do it.
<form onSubmit={handleSubmit(onSubmit)}> ... </form>
✅ Cleaner logic. Prevents bugs from unchecked validation states.
✅ 4. Reset form after success using reset()
This improves user experience and prepares the form for the next entry.
const onSubmit = (data) => {
alert("Form submitted!");
reset(); // clears all inputs
};
✅ Especially helpful for feedback/contact forms and wizards.
✅ 5. Use defaultValues
for edit forms
If you're editing data (e.g., user profile), preload existing values.
const { register } = useForm({
defaultValues: {
name: "John Doe",
email: "john@example.com"
}
});
✅ This avoids the "blank form when editing" problem.
✅ 6. Validate related fields with watch()
or validate
For example, check that confirm password
matches password
.
const password = watch("password");
<input
{...register("confirm", {
validate: value => value === password || "Passwords do not match"
})}
/>
✅ Great for signup, password reset, and banking apps.
✅ 7. Use a schema for large forms (with Yup)
Keep form logic separate and more maintainable using yup
with resolver
.
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
const schema = yup.object().shape({
email: yup.string().required().email(),
password: yup.string().min(6).required()
});
const { register } = useForm({ resolver: yupResolver(schema) });
✅ Scales well for enterprise forms and APIs.
🌍 Real-World Examples
-
1. Login Form
Used in almost every web app — from Facebook to Slack.
✅react-hook-form
tracksemail
andpassword
, validates required fields, and prevents empty submission with fewer lines of code. -
2. Signup Form
Seen in platforms like Gmail, LinkedIn, or any SaaS product.
✅ Validates fields likename
,email
,password
, andconfirm password
withvalidate
andwatch()
. -
3. Contact Form
Used on websites like Shopify, Stripe, or small business sites.
✅ Usesreact-hook-form
to track message input, required name/email, and sends the data via API on submit. -
4. E-commerce Checkout Form
Found on Amazon, eBay, or Shopify stores.
✅ Capturesshipping address
,billing
, andpayment info
using multiple steps and resets or progresses with `reset()` and `setValue()`. -
5. Edit Profile / Account Settings
Used in apps like Twitter, Notion, or Instagram.
✅ UsesdefaultValues
to pre-fill the form with current user data, allowing smooth edits. -
6. Admin Form to Add Products or Users
Seen in dashboards like Shopify Admin or Contentful.
✅ UsesuseFieldArray()
to dynamically add/remove product variants or features. -
7. Survey or Quiz Application
Used in platforms like Google Forms, Typeform, or Kahoot.
✅ Handles dynamic questions withuseFieldArray()
and tracks all answers usingregister()
.
Formik + Yup
🧠 Detailed Explanation
Formik is a library that helps you build and manage forms in React.
Instead of writing a lot of code to track input values, check for errors, and handle form submission manually, Formik does most of the work for you.
Now add Yup — a validation library — and you get a powerful combo. Yup helps you define rules like:
- Is the field required?
- Should the input be an email?
- Is the password long enough?
Formik + Yup work perfectly together. You define all your validation rules once using Yup, and Formik uses those rules to automatically show error messages and prevent wrong form submissions.
🎯 How It Works in Simple Steps
- You create a Yup schema that lists what is required and what format each field should be in.
- You give that schema to Formik using the
validationSchema
setting. - Formik uses it to check inputs and shows error messages if anything is wrong.
- When everything is okay, your
onSubmit()
function runs.
💡 Quick Example:
email: Yup.string()
.email("Email is not valid")
.required("Email is required")
password: Yup.string()
.min(6, "Too short!")
.required("Password is required")
✅ When the user types something invalid, they see the message right below the input field. No extra code needed.
🔍 In Short:
Formik handles the form.
Yup handles the rules.
Together, they save time, reduce bugs, and make your forms cleaner and more professional.
🛠️ Best Implementation
This example shows how to build a clean and validated login form using Formik for form state and Yup for validation rules.
✅ We’ll include:
- Formik for managing input values, touched state, errors, and submit
- Yup for schema-based field validation
- Custom error messages
- Best practices like reusable validation, clear initial values, and proper structure
✅ Full Working Example
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
// Step 1: Create validation schema
const LoginSchema = Yup.object().shape({
email: Yup.string()
.email('Enter a valid email')
.required('Email is required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
});
function LoginForm() {
return (
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={LoginSchema}
onSubmit={(values, { resetForm }) => {
console.log('Submitted:', values);
alert('Login successful!');
resetForm(); // Clear form after submit
}}
>
{() => (
<Form style={{ maxWidth: '400px' }}>
<h3>Login</h3>
<div>
<label>Email</label><br />
<Field name="email" placeholder="you@example.com" /><br />
<ErrorMessage name="email" component="div" style={{ color: 'red' }} />
</div>
<div style={{ marginTop: '1rem' }}>
<label>Password</label><br />
<Field name="password" type="password" placeholder="******" /><br />
<ErrorMessage name="password" component="div" style={{ color: 'red' }} />
</div>
<button type="submit" style={{ marginTop: '1rem' }}>Login</button>
</Form>
)}
</Formik>
);
}
📘 Step-by-Step Breakdown
- Step 1: Define a Yup schema with `.shape({})` to set rules for each field
- Step 2: Pass that schema into Formik’s
validationSchema
prop - Step 3: Wrap the form in
<Formik>
and use<Form>
to auto-handle submission - Step 4: Use
<Field name="..." />
to bind inputs - Step 5: Use
<ErrorMessage name="..." />
to display validation messages
✅ Why This is the Best Practice
- 🔁 Validation logic is reusable, centralized, and readable
- 🧼 Form code is clean — no need for manual state management
- 🚫 Prevents common issues like empty fields or wrong format
- 💾 Easily connected to backend APIs by replacing
alert()
withaxios.post()
or similar
💡 Examples
✅ Goal: Create a login form with two fields — email and password — and validate them using Yup
.
We’ll use:
- Formik to manage form state and handle submission
- Yup to validate input values (required, email format, password length)
- Field for inputs and ErrorMessage to display validation messages
📦 Full Example: Login Form with Formik + Yup
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
// 1. Define validation schema using Yup
const LoginSchema = Yup.object().shape({
email: Yup.string()
.email('Enter a valid email')
.required('Email is required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
});
function LoginForm() {
return (
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={LoginSchema}
onSubmit={(values, { resetForm }) => {
console.log(values);
alert(`Welcome, ${values.email}`);
resetForm(); // clears the form
}}
>
<Form style={{ maxWidth: '400px' }}>
<h3>Login</h3>
<div>
<label>Email:</label><br />
<Field name="email" placeholder="you@example.com" /><br />
<ErrorMessage name="email" component="div" style={{ color: 'red' }} />
</div>
<div style={{ marginTop: '1rem' }}>
<label>Password:</label><br />
<Field name="password" type="password" placeholder="******" /><br />
<ErrorMessage name="password" component="div" style={{ color: 'red' }} />
</div>
<button type="submit" style={{ marginTop: '1rem' }}>Login</button>
</Form>
</Formik>
);
}
🧠 Explanation
- ✅
initialValues
: sets default input values - ✅
validationSchema
: imports Yup rules and connects them to Formik - ✅
onSubmit
: triggered only if validation passes - ✅
<Field />
: automatically tracks and updates field values - ✅
<ErrorMessage />
: shows validation error message for each field
⚠️ Validation Rules Used
email
: must be a valid email format, and requiredpassword
: must be at least 6 characters, and required
✅ Why This Is a Good Example
- Clean structure with validation clearly separated using Yup
- No need to use
useState
or manual error handling - Can easily be reused in signup, reset password, or profile forms
- Provides instant user feedback if validation fails
🔁 Alternatives
- react-hook-form + Yup: more performant with fewer re-renders
- Native useState + validation: good for tiny forms, but more boilerplate
❓ General Q&A
Q: What is Formik?
A: Formik is a library for React that helps you build and manage forms more easily. Instead of writing separate useState
for each input and managing error states manually, Formik takes care of it all — tracking field values, validation, and form submission for you.
It gives you tools like <Field />
for inputs and <ErrorMessage />
for error handling, so you don’t have to write repetitive code.
Q: What is Yup?
A: Yup is a JavaScript library that lets you define validation rules using a clean and readable syntax. It’s often used with Formik to check if the user entered the correct type of data — like a valid email, minimum password length, or required fields.
Think of Yup like a checklist for each form field: “Is it filled in?”, “Is it the right format?”, “Is it long enough?”
Q: Why use Formik and Yup together?
A: Formik helps you manage form inputs and submission. Yup helps you validate the input values. Together, they make building forms faster, safer, and cleaner.
Formik automatically connects with Yup using the validationSchema
prop. This means you don’t need to write manual validation logic — it’s all handled for you.
Q: How does Formik handle form submission?
A: Formik uses a function called onSubmit
. It only runs when the form passes all validation checks. You don’t have to manually check for errors — Formik does that before calling your submit logic.
onSubmit={(values) => {
console.log(values);
alert("Form submitted!");
}}
Q: What’s the difference between Formik and using useState?
A: With useState
, you manually track each input value, handle changes, and validate fields. This works fine for small forms, but becomes messy in big forms.
Formik automates this. You don’t need to write separate onChange
handlers or validation logic — Formik manages it all inside the <Formik>
wrapper.
Q: Is Formik used in real-world apps?
A: Yes! Formik is widely used in production React apps — from small startups to enterprise SaaS platforms. It’s especially useful in login/signup forms, dashboards, admin panels, and settings/profile forms where you need validation and clean UX.
Q: Can I reuse my Yup validation?
A: Absolutely. Yup schemas can be reused across multiple forms. You can create a validation file (like schemas.js
) and export your schema objects from there. This keeps your code clean and consistent.
🛠️ Technical Q&A
Q1: How do I make a field required using Yup?
A: Use the required()
method in the Yup schema. Formik will automatically prevent form submission and show an error if it's empty.
Yup.object({
email: Yup.string()
.required('Email is required')
})
✅ When the user leaves the email field empty, Formik will show “Email is required”.
Q2: How do I validate an email format?
A: Use the email()
method from Yup. It checks if the entered value matches a valid email format.
Yup.object({
email: Yup.string()
.email('Invalid email format')
.required('Email is required')
})
✅ This checks both: that the field is not empty and that it looks like an email.
Q3: How do I validate a minimum password length?
A: Use the min()
method inside the Yup password rule.
Yup.object({
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required')
})
✅ Prevents weak or too-short passwords.
Q4: How do I check if password and confirm password match?
A: Use oneOf
in Yup and reference the other field using Yup.ref()
.
Yup.object({
password: Yup.string().required(),
confirm: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required('Confirm password is required')
})
✅ Great for signup and reset password forms.
Q5: How do I handle form submission in Formik?
A: Formik provides an onSubmit
function that is called only when validation passes.
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={LoginSchema}
onSubmit={(values) => {
console.log('Form Data:', values);
}}
>
✅ You only get clean, validated data inside onSubmit
.
Q6: How do I reset the form after submission?
A: Use Formik’s helper method resetForm()
inside the onSubmit
block.
onSubmit={(values, { resetForm }) => {
alert('Submitted!');
resetForm(); // clears all fields
}}
✅ Especially useful in contact forms or after success messages.
Q7: How do I show validation errors in the UI?
A: Use Formik’s <ErrorMessage name="field" />
component. It automatically displays the error under that field.
<Field name="email" />
<ErrorMessage name="email" component="div" />
✅ No need to write manual if-else checks for each field.
✅ Best Practices
✅ 1. Use a Yup schema for validation (not manual checks)
Why? It keeps validation logic separate, reusable, and readable.
// ✅ Good
const schema = Yup.object({
email: Yup.string().email().required(),
password: Yup.string().min(6).required()
});
❌ Avoid doing validation inside the `onSubmit()` handler manually.
✅ 2. Use <Field />
and <ErrorMessage />
for cleaner JSX
Why? They’re connected to Formik internally and reduce boilerplate.
<Field name="email" placeholder="Email" />
<ErrorMessage name="email" component="div" className="error" />
✅ No need for separate state or event handlers.
✅ 3. Use resetForm()
after submission
Why? It gives users a clean form after success, especially in contact forms or surveys.
onSubmit={(values, { resetForm }) => {
console.log(values);
resetForm(); // clears the form
}}
✅ 4. Use initialValues
and defaultProps
for reusability
Why? Makes it easy to reuse the form for edit/update mode too.
<Formik
initialValues={props.data || { email: '', password: '' }}
...
>
✅ Useful in admin panels or profile pages.
✅ 5. Keep validation schemas in a separate file
Why? Keeps form component clean and separates logic from layout.
// validationSchemas.js
export const LoginSchema = Yup.object({
email: Yup.string().email().required(),
password: Yup.string().min(6).required()
});
// LoginForm.js
import { LoginSchema } from './validationSchemas';
✅ 6. Reuse form structure with props
Why? To use the same form for create and update, pass initial values as props.
<LoginForm initialEmail="user@example.com" />
✅ Makes forms dynamic and reusable.
✅ 7. Wrap forms in a layout div with spacing and mobile styles
Why? Improves UX and responsiveness.
<form style={{ maxWidth: 400, padding: '1rem' }}>
<Field name="email" />
<Field name="password" />
<button type="submit">Submit</button>
</form>
🌍 Real-World Examples
-
1. User Login Form
Use Case: Allow users to log in to your app with email and password.
Formik + Yup Role: Automatically validate required fields, email format, and prevent submission with empty or invalid data.
Where it’s used: Every SaaS platform, from Netflix to Slack.
-
2. Signup Form
Use Case: Create a new user account with fields like name, email, password, and confirm password.
Formik + Yup Role: Use Yup’s
oneOf()
to ensure both passwords match and all fields are validated before submit.Where it’s used: Apps like Instagram, Twitter, Gmail.
-
3. User Profile Update Form
Use Case: Allow users to update personal info like name, email, and phone number.
Formik + Yup Role: Preload data using
initialValues
, validate updates, and show errors clearly.Where it’s used: Settings pages in LinkedIn, Notion, Figma.
-
4. E-commerce Checkout Form
Use Case: Collect customer shipping address and payment information.
Formik + Yup Role: Ensure fields like ZIP code, phone number, and card data are correctly filled using Yup patterns.
Where it’s used: Amazon, Shopify, Stripe checkout flows.
-
5. Contact or Feedback Form
Use Case: Users send feedback, bug reports, or general inquiries via a form.
Formik + Yup Role: Require fields like name, email, and message. Auto-clear on submit using
resetForm()
.Where it’s used: Company websites like Zendesk, Spotify.
-
6. Admin Product Form
Use Case: Admin creates or updates products with fields like title, price, category, image, and stock.
Formik + Yup Role: Validate number ranges (e.g. price > 0), upload fields, and nested data for categories or tags.
Where it’s used: Admin dashboards in Shopify, Contentful, CMS apps.
-
7. Multi-Step Survey or Signup Flow
Use Case: Guide users through multiple steps like personal info → preferences → summary.
Formik + Yup Role: Use
validationSchema
per step, andsetFieldValue
ornextStep()
to navigate.Where it’s used: Typeform, HubSpot, job application portals.
Error Handling & Validations in React
🧠 Detailed Explanation
In any web app, forms are everywhere — login, signup, search, checkout.
Validation means checking if the user filled the form correctly.
Error handling means managing what happens when something goes wrong (like a failed API or invalid input).
✅ What is Validation?
Validation helps make sure the data is:
- ✔️ Present (not empty)
- ✔️ In the right format (like email looks like an email)
- ✔️ Meets your rules (like password is 6+ characters)
Example: If someone leaves the email field blank, the app should say “Email is required” instead of crashing or sending bad data.
✅ What is Error Handling?
Error handling is about catching problems and showing a helpful message instead of breaking the app.
- 📦 If your API is down → Show “Something went wrong”
- 🚫 If a user types wrong credentials → Show “Invalid login”
- 🌐 If network fails → Show “Check your internet connection”
✅ Why Are These Important?
- They keep the user experience smooth
- They prevent bugs and broken data from reaching your backend
- They help users fix mistakes quickly
🛠 How Is It Done in React?
- Formik or react-hook-form to manage the form
- Yup for defining validation rules
try/catch
blocks to catch async errors like API failuresErrorBoundary
to catch crashes in UI components
🧪 Real Example:
You're building a login form:
- User leaves email blank → show “Email is required”
- User enters wrong password → show “Invalid credentials”
- Server is down → show “Something went wrong”
With validation + error handling, your app feels safe, clean, and user-friendly ✨
🛠️ Best Implementation
This example creates a login form with:
- Validation using
Yup
- Form handling via
react-hook-form
- Network error handling using
try/catch
- Error messages shown per field + global error area
✅ Code Implementation
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import * as Yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
// 1. Validation schema
const schema = Yup.object().shape({
email: Yup.string().email('Invalid email').required('Email is required'),
password: Yup.string().min(6, 'Too short').required('Password is required'),
});
export default function LoginForm() {
const [serverError, setServerError] = useState('');
const {
register,
handleSubmit,
formState: { errors },
reset
} = useForm({
resolver: yupResolver(schema),
});
// 2. Submit handler with error handling
const onSubmit = async (data) => {
try {
setServerError('');
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) throw new Error('Invalid credentials');
const result = await response.json();
alert('Welcome ' + result.name);
reset();
} catch (err) {
setServerError(err.message || 'Something went wrong');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} style={{ maxWidth: '400px' }}>
<h3>Login</h3>
{serverError && <div style={{ color: 'red', marginBottom: '1rem' }}>
{serverError}
</div>}
<div>
<label>Email:</label><br />
<input {...register('email')} placeholder="you@example.com" /><br />
{errors.email && <small style={{ color: 'red' }}>{errors.email.message}</small>}
</div>
<div style={{ marginTop: '1rem' }}>
<label>Password:</label><br />
<input type="password" {...register('password')} placeholder="******" /><br />
{errors.password && <small style={{ color: 'red' }}>{errors.password.message}</small>}
</div>
<button type="submit" style={{ marginTop: '1rem' }}>Login</button>
</form>
);
}
📘 Breakdown of Best Practices
- ✅
useForm()
fromreact-hook-form
manages form state efficiently - ✅
yupResolver()
connects Yup schema for validation - ✅
try/catch
wraps the API to catch and show global errors (e.g. login failed) - ✅ Field-level errors are displayed right under each input
- ✅
reset()
clears form on success - ✅ Form won't submit if validation fails — no extra logic required
🌍 Where to Use This
- Login / Signup forms
- Contact / Feedback pages
- Admin input forms with backend validation
- Forms that depend on async results (e.g. checking availability, email duplicates)
💡 Examples
📦 Scenario: Login Form with Validation + API Error Handling
Let’s build a real-world login form that:
- ✅ Validates
email
andpassword
- ✅ Shows error messages below fields
- ✅ Handles API errors like wrong credentials
- ✅ Resets form after success
🧱 Step-by-Step Code
// 1. Import required tools
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import * as Yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
// 2. Create a Yup validation schema
const schema = Yup.object().shape({
email: Yup.string().email('Must be a valid email').required('Email is required'),
password: Yup.string().min(6, 'Password too short').required('Password is required'),
});
function LoginForm() {
const [apiError, setApiError] = useState('');
// 3. Initialize the form
const {
register,
handleSubmit,
formState: { errors },
reset
} = useForm({
resolver: yupResolver(schema)
});
// 4. Define the submit function with error handling
const onSubmit = async (data) => {
try {
setApiError(''); // reset old API errors
const res = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
});
if (!res.ok) throw new Error('Invalid email or password');
const result = await res.json();
alert('Login successful! Welcome ' + result.name);
reset(); // clear the form
} catch (err) {
setApiError(err.message || 'Something went wrong');
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} style={{ maxWidth: 400 }}>
<h3>Login Form</h3>
{apiError && <div style={{ color: 'red' }}>{apiError}</div>}
<div>
<label>Email:</label><br />
<input {...register('email')} /><br />
{errors.email && <small style={{ color: 'red' }}>{errors.email.message}</small>}
</div>
<div style={{ marginTop: '1rem' }}>
<label>Password:</label><br />
<input type="password" {...register('password')} /><br />
{errors.password && <small style={{ color: 'red' }}>{errors.password.message}</small>}
</div>
<button type="submit" style={{ marginTop: '1rem' }}>Login</button>
</form>
);
}
📘 What’s Happening Here?
- Yup defines validation rules
- react-hook-form manages input state + validation
- try/catch handles errors from the API
apiError
state is shown when something goes wrongreset()
clears the form after success
✅ User Experience
- Users see **inline field errors** if something’s wrong with their input
- If the **API rejects the login**, they get a friendly error like “Invalid login”
- When everything works, they get a success message and the form clears
This pattern is used in login forms, signup flows, checkout pages, admin dashboards, and anywhere else you handle user input.
🔁 Alternatives
- Use custom hooks like
useError
to manage error state globally - Use inline validation without libraries for smaller forms
- Use
componentDidCatch
andgetDerivedStateFromError
for error boundaries
❓ General Q&A
Q: What is validation in a form?
A: Validation is the process of checking if the user’s input is correct before it is submitted.
Example: If a user leaves the email field empty, the app should show “Email is required.”
It helps prevent mistakes like:
- ❌ Submitting empty fields
- ❌ Entering a phone number in the email field
- ❌ Using a password that’s too short
Q: What is error handling in React?
A: Error handling means detecting when something goes wrong (like an API failure or invalid input) and showing a helpful message instead of letting the app break or crash.
Example: If a login API returns an error, the app should say “Invalid credentials” instead of showing a blank page or crashing.
Q: Why are validation and error handling important?
A: They make your app safe and user-friendly. Without them:
- 🔴 Users can enter broken or dangerous data
- 🔴 The app might crash unexpectedly
- 🔴 Users won’t know what went wrong
With proper validation and error messages, users feel guided and supported.
Q: When should I use try/catch in React?
A: Use try/catch
when making async calls (like fetch()
or axios()
) to handle network or server errors.
try {
const res = await fetch('/api/login');
const data = await res.json();
} catch (err) {
setError('Something went wrong. Please try again.');
}
Q: What tools can I use for form validation?
A: The most common tools in React are:
- ✅ react-hook-form: For form state management
- ✅ Yup: For validation rules and schemas
- ✅ Formik: Another form library with built-in validation support
Q: How do I show field errors below the input?
A: Most libraries give you access to an errors
object. You can show the message like this:
{errors.email && <p style={{color: 'red'}}>{errors.email.message}</p>}
Q: What happens if I don’t handle errors?
A: Your app may:
- ⚠️ Crash on unexpected input
- ⚠️ Send wrong or incomplete data to the server
- ⚠️ Confuse the user with no feedback
That’s why handling both frontend (validation) and backend (API) errors is essential for a smooth user experience.
🛠️ Technical Q&A
Q1: How do I validate a form in React without any library?
A: You can use native HTML and simple state logic, but it becomes messy in large forms. Here's a minimal example:
const [email, setEmail] = useState('');
const [error, setError] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!email.includes('@')) {
setError('Invalid email');
} else {
setError('');
// submit logic
}
};
✅ Works for small forms, but better to use libraries like `react-hook-form` for larger ones.
Q2: How do I use Yup with react-hook-form?
A: Install `@hookform/resolvers` and Yup, then connect them via `yupResolver`.
import { useForm } from 'react-hook-form';
import * as Yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
const schema = Yup.object({
email: Yup.string().email().required(),
});
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(schema)
});
✅ This setup gives you powerful validation rules in one place.
Q3: How do I catch API errors using try/catch in a form submit?
A: Use `try/catch` around your async call and display errors to users.
const onSubmit = async (data) => {
try {
const res = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Login failed');
} catch (err) {
setApiError(err.message);
}
};
✅ Prevents unhandled promise errors and lets you show messages like “Login failed.”
Q4: How do I show errors below inputs using react-hook-form?
A: Use the `errors` object provided by `formState`.
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
✅ Users get immediate feedback tied to each field.
Q5: How do I validate matching passwords in a signup form?
A: Use `Yup.ref()` to compare two fields.
Yup.object({
password: Yup.string().required(),
confirm: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required()
})
✅ Common in registration forms.
Q6: How do I catch component rendering errors in React?
A: Use an Error Boundary — a class component with `componentDidCatch()`.
class ErrorBoundary extends React.Component {
state = { hasError: false };
componentDidCatch(error, info) {
this.setState({ hasError: true });
}
render() {
return this.state.hasError
? <h2>Something broke</h2>
: this.props.children;
}
}
✅ This prevents total app crashes from rendering bugs.
Q7: Can I show multiple error types (field + API) together?
A: Yes! Display field errors below each field, and an API error at the top.
{apiError && <div className="error-banner">{apiError}</div>}
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
✅ Gives full feedback: “Field is missing” + “Login failed.”
✅ Best Practices
✅ 1. Use schema-based validation (e.g. Yup)
Why? Keeps validation logic reusable, centralized, and easier to maintain.
const schema = Yup.object({
email: Yup.string().email().required(),
password: Yup.string().min(6).required(),
});
❌ Avoid inline if-else logic for each field unless it’s a tiny form.
✅ 2. Use a form library like react-hook-form
or Formik
Why? Handles form state, error tracking, and performance efficiently.
const { register, handleSubmit, formState: { errors } } = useForm({ resolver: yupResolver(schema) });
✅ Avoids re-renders and provides built-in error handling.
✅ 3. Show field-level validation errors
Why? Helps users quickly find and fix issues.
<input {...register("email")} />
{errors.email && <p>{errors.email.message}</p>}
✅ Don’t show all errors at the top — use contextual hints.
✅ 4. Show API or server errors at the top of the form
Why? Let users know when something fails after submission.
{apiError && <div className="error-banner">{apiError}</div>}
✅ Useful for errors like “Email already exists” or “Invalid credentials.”
✅ 5. Use reset()
after successful submission
Why? Clears fields and gives users visual confirmation.
onSubmit={(data, { reset }) => {
alert("Success!");
reset(); // clears form
}};
✅ Avoid leaving stale data in inputs.
✅ 6. Use try/catch
for async error handling
Why? Prevents app crashes and enables helpful error messages.
try {
const res = await fetch('/api');
if (!res.ok) throw new Error('API failed');
} catch (err) {
setApiError(err.message);
}
✅ Don't forget to check res.ok
after fetch.
✅ 7. Use Error Boundaries to catch rendering errors
Why? Prevents the whole app from crashing if one component fails.
class ErrorBoundary extends React.Component {
state = { hasError: false };
componentDidCatch(error) { this.setState({ hasError: true }); }
render() {
return this.state.hasError ? <h2>Something went wrong</h2> : this.props.children;
}
}
✅ Use at layout level to cover large UI sections.
🌍 Real-World Examples
-
1. Login Form
Use Case: A user enters email and password to log in.
Validation: Yup ensures email format and minimum password length.
Error Handling: try/catch handles failed API login (e.g., wrong password), and shows “Invalid credentials” message.
-
2. Signup Form
Use Case: A user registers with name, email, password, and confirm password.
Validation: Yup checks password confirmation using
Yup.ref()
.Error Handling: Server error (like “Email already in use”) is shown above the form.
-
3. Contact Form
Use Case: A user fills a message form to contact support.
Validation: All fields required, message must be at least 10 characters.
Error Handling: Catch fetch errors (e.g., server is offline) with try/catch, and show “Please try again later.”
-
4. Checkout Form
Use Case: A user enters billing info and card details.
Validation: Required fields, card number format, postal code pattern.
Error Handling: Catch Stripe/PayPal errors (like card declined) and show user-friendly message.
-
5. Admin - Add Product
Use Case: Admin enters product name, price, image, and quantity.
Validation: Price must be positive number, name required, image must be uploaded.
Error Handling: If image upload fails or API returns 500, show “Product save failed.”
-
6. Profile Settings
Use Case: User updates their name, email, or password.
Validation: Yup ensures valid email, password minimum 6 characters.
Error Handling: If update API fails (e.g., session expired), redirect to login or show “Update failed.”
-
7. Multi-Step Form Wizard
Use Case: User fills out a multi-step onboarding form (personal info → address → preferences).
Validation: Validate each step with Yup schema and show errors before allowing next step.
Error Handling: API call at the end is wrapped in try/catch. If error, stay on summary step and show detailed message.
Fetch / Axios in React
🧠 Detailed Explanation
In React, you often need to get data from an API or send data to a server — for example, to:
- 📰 Load blog posts
- 👤 Get user info
- 🛒 Submit a checkout form
To do that, you use tools like Fetch or Axios.
🌐 What is fetch()
?
Fetch is a built-in browser function that lets you make network requests.
Think of it like asking the server: “Can I have this data?”
Example:
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => console.log(data));
📌 It's simple and always available — no need to install anything.
📦 What is Axios?
Axios is a library you can install in React projects to make HTTP requests.
It works like fetch, but it’s easier to use and has more features built-in.
Example:
import axios from 'axios';
axios.get('https://api.example.com/users')
.then(res => console.log(res.data));
📌 Axios automatically converts response to JSON, handles errors better, and lets you set headers (like tokens) easily.
🆚 What’s the difference?
Feature | Fetch | Axios |
---|---|---|
Installation | No | Yes (via npm) |
Convert to JSON? | Yes, manually | No, done for you |
Handle Errors | Basic | Better, with try/catch or interceptors |
Cancel Requests | Needs AbortController | Built-in with cancel tokens |
Recommended for | Small/simple apps | Mid-large apps or authenticated APIs |
🚀 When to use them?
- ✅ Use fetch for small apps or static data
- ✅ Use axios if you need:
- – Better error handling
- – Custom headers (like auth tokens)
- – Interceptors (e.g., log or retry on error)
Both work great — choose based on your needs! 💡
🛠️ Best Implementation
🎯 Goal:
Fetch user data from an API and display it. We'll use both Fetch and Axios versions.
✅ Version 1: Using fetch()
(Native)
import React, { useEffect, useState } from 'react';
function UsersWithFetch() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
const getUsers = async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
if (!res.ok) throw new Error('Failed to fetch users');
const data = await res.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
getUsers();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p style={{ color: 'red' }}>{error}</p>;
return (
<div>
<h3>Users (Fetched with fetch())</h3>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
✅ Version 2: Using Axios
(Simpler Syntax)
import React, { useEffect, useState } from 'react';
import axios from 'axios';
function UsersWithAxios() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
let isMounted = true; // cleanup flag
axios.get('https://jsonplaceholder.typicode.com/users')
.then(res => {
if (isMounted) setUsers(res.data);
})
.catch(err => {
if (isMounted) setError('Axios error: ' + err.message);
})
.finally(() => {
if (isMounted) setLoading(false);
});
return () => { isMounted = false }; // cleanup on unmount
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p style={{ color: 'red' }}>{error}</p>;
return (
<div>
<h3>Users (Fetched with Axios)</h3>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
📘 Key Best Practices Used
- ✅ Use
async/await
withfetch()
for readability - ✅ Always wrap API calls in
try/catch
blocks to handle errors - ✅ Use
finally
to end loading state regardless of outcome - ✅ Use cleanup flags to prevent setting state on unmounted components
- ✅ Prefer Axios when you need interceptors, timeouts, or token headers
🧪 Bonus Tips
- ✅ Use Axios interceptors for global error or token handling
- ✅ Create a custom hook like
useFetch
oruseAxios
to reuse logic - ✅ Store API URLs in
.env
files and useprocess.env.REACT_APP_API_URL
💡 Examples
📦 Goal:
We want to fetch a list of users from an API (https://jsonplaceholder.typicode.com/users
) and show them in a list.
🔹 Example 1: Using fetch()
(built-in)
import React, { useEffect, useState } from 'react';
function UsersWithFetch() {
const [users, setUsers] = useState([]); // store data
const [loading, setLoading] = useState(true); // show loading
const [error, setError] = useState(''); // catch errors
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((res) => {
if (!res.ok) throw new Error('Network error'); // handle HTTP errors
return res.json();
})
.then((data) => setUsers(data))
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p style={{ color: 'red' }}>{error}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
✅ **Explanation:**
fetch()
calls the API.json()
parses the responsesetUsers(data)
stores the resultcatch()
handles any errorsfinally()
ends loading state
🔹 Example 2: Using axios
(library)
import React, { useEffect, useState } from 'react';
import axios from 'axios'; // npm install axios
function UsersWithAxios() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
let isMounted = true;
axios.get('https://jsonplaceholder.typicode.com/users')
.then((response) => {
if (isMounted) setUsers(response.data);
})
.catch((err) => {
if (isMounted) setError(err.message);
})
.finally(() => {
if (isMounted) setLoading(false);
});
return () => { isMounted = false }; // prevent state update if unmounted
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p style={{ color: 'red' }}>{error}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
✅ **Explanation:**
axios.get()
calls the APIresponse.data
is already parsed- No need for
.json()
- More readable and easier to handle complex headers
✨ Extra Features Axios Supports (but Fetch Doesn’t by Default):
- ✅ Automatically handles JSON parsing
- ✅ Lets you add headers easily (e.g., Auth tokens)
- ✅ Supports interceptors (e.g., retry on fail)
- ✅ Supports cancellation of requests
🔁 Alternatives
- React Query – For automatic caching, retrying, and real-time sync
- SWR – Lightweight fetcher from Vercel for caching & revalidation
- SuperAgent – Another request library with a different style
❓ General Q&A
Q: What is Fetch in React?
A: fetch()
is a built-in browser function that lets you request data from a server or API. You can use it in React to get or send information like blog posts, users, products, etc.
Example:
fetch('https://api.example.com/data')
.then(res => res.json())
.then(data => console.log(data));
✅ Use Fetch when you want a lightweight, native option and don’t need advanced features.
Q: What is Axios?
A: Axios is a popular JavaScript library (not built-in) for making HTTP requests. It works like fetch()
but is easier to use and has more features like auto JSON parsing, interceptors, and error handling.
Example:
import axios from 'axios';
axios.get('https://api.example.com/data')
.then(res => console.log(res.data));
✅ Use Axios in React apps when your API usage grows or you need custom headers, error handling, or token-based auth.
Q: Do I need to install Fetch or Axios?
A:
- 🚫 Fetch is built into all modern browsers — no installation needed
- ✅ Axios must be installed with
npm install axios
oryarn add axios
Q: Which one is better — Fetch or Axios?
A: Neither is “better,” but they suit different needs:
- ✅ Use Fetch for small/simple requests
- ✅ Use Axios when you need better error handling, token headers, or request interceptors
Q: How do I handle errors with Fetch?
A: Fetch doesn't throw an error for HTTP 400/500 responses, so you must check res.ok
yourself.
fetch('/api')
.then(res => {
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
})
.then(data => console.log(data))
.catch(err => console.error(err));
✅ Axios throws errors automatically when response status is not 200, making it easier to handle.
Q: Can I use async/await with Fetch and Axios?
A: Yes! You can use async/await
with both for cleaner and more readable code.
// Using Fetch
const getData = async () => {
const res = await fetch('/api');
const data = await res.json();
console.log(data);
};
// Using Axios
const getData = async () => {
const res = await axios.get('/api');
console.log(res.data);
};
Q: Can I use Axios in class components?
A: Yes! Axios works in both function and class components. In class components, you can use it inside componentDidMount()
.
class Users extends React.Component {
state = { users: [] };
componentDidMount() {
axios.get('/api/users')
.then(res => this.setState({ users: res.data }));
}
render() {
return <ul>{this.state.users.map(u => <li>{u.name}</li>)}</ul>;
}
}
🛠️ Technical Q&A
Q1: How do I fetch data with async/await using fetch()?
A: Use async
inside useEffect
to call fetch()
and handle JSON + errors.
useEffect(() => {
const getData = async () => {
try {
const res = await fetch('/api/data');
if (!res.ok) throw new Error('Failed to load');
const json = await res.json();
setData(json);
} catch (err) {
setError(err.message);
}
};
getData();
}, []);
✅ Always check res.ok
to catch 400/500 status manually.
Q2: How do I send a POST request with fetch()?
A: Use method: 'POST'
, set headers
, and use JSON.stringify()
on the body.
const handleSubmit = async () => {
const res = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
});
const data = await res.json();
console.log(data);
};
✅ You must stringify the body yourself when using fetch.
Q3: How do I send a POST request with Axios?
A: Axios handles headers and body formatting for you.
import axios from 'axios';
const handleSubmit = async () => {
const response = await axios.post('/api/login', {
email: 'user@example.com',
password: '123456'
});
console.log(response.data);
};
✅ Cleaner syntax — no need for JSON.stringify.
Q4: How do I set custom headers in Axios?
A: Pass a second argument to your request with a headers
object.
axios.get('/api/users', {
headers: {
Authorization: `Bearer ${token}`
}
});
✅ Useful for authentication, tracking, or API keys.
Q5: How do I handle global errors in Axios (like 401 or 500)?
A: Use axios.interceptors.response
to catch all errors in one place.
axios.interceptors.response.use(
res => res,
err => {
if (err.response.status === 401) {
// logout or redirect
}
return Promise.reject(err);
}
);
✅ Great for token expiration and app-wide error handling.
Q6: How do I cancel an Axios request when the component unmounts?
A: Use a Cancel Token or AbortController
(for Fetch).
useEffect(() => {
const controller = new AbortController();
axios.get('/api/data', { signal: controller.signal })
.then(res => setData(res.data))
.catch(err => {
if (axios.isCancel(err)) {
console.log('Request cancelled');
}
});
return () => controller.abort();
}, []);
✅ Prevents memory leaks and unnecessary state updates.
Q7: How do I handle both loading and error states?
A: Use three states: loading
, error
, and data
.
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
axios.get('/api/data')
.then(res => setData(res.data))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, []);
✅ Gives better UX with spinners and error messages.
✅ Best Practices
✅ 1. Use async/await
instead of chaining .then()
Why? It makes your code easier to read and debug.
// ✅ Good
const fetchData = async () => {
try {
const res = await fetch('/api/data');
const data = await res.json();
setData(data);
} catch (err) {
console.error(err);
}
};
// ❌ Avoid chaining .then() everywhere unless really simple
fetch('/api/data')
.then(res => res.json())
.then(setData);
✅ 2. Always show loading and error states
Why? It improves user experience and feedback.
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
axios.get('/api/posts')
.then(res => setData(res.data))
.catch(err => setError('Failed to load'))
.finally(() => setLoading(false));
}, []);
✅ This prevents a blank screen or user confusion when something fails.
✅ 3. Check res.ok
when using fetch()
Why? Fetch won’t throw errors on 404 or 500 by default.
const res = await fetch('/api');
if (!res.ok) throw new Error('Failed to fetch');
✅ Axios does this automatically, but with Fetch, you must add it.
✅ 4. Use Axios interceptors to handle auth errors globally
Why? Avoid repeating 401 logic in every component.
axios.interceptors.response.use(
res => res,
err => {
if (err.response.status === 401) {
// e.g., logout user or refresh token
}
return Promise.reject(err);
}
);
✅ Makes your error handling DRY (Don’t Repeat Yourself).
✅ 5. Store API URLs and tokens in .env
files
Why? Avoid hardcoding sensitive data into your codebase.
// .env
REACT_APP_API_URL=https://api.example.com
// inside component
axios.get(`${process.env.REACT_APP_API_URL}/posts`);
✅ Helps you manage staging, dev, and production environments.
✅ 6. Cancel pending requests when components unmount
Why? Prevents memory leaks and unwanted state updates.
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
return () => controller.abort();
}, []);
✅ Axios also supports cancel tokens for the same purpose.
✅ 7. Create reusable API service functions
Why? Keeps components clean and separates concerns.
// api.js
export const getUsers = () => axios.get('/api/users');
// Users.js
useEffect(() => {
getUsers().then(res => setUsers(res.data));
}, []);
✅ Easy to test and mock in unit tests.
🌍 Real-World Examples
-
1. User Login
Use Case: A user logs in by entering email and password.
Request:
POST
request to/api/login
Best with:
Axios
— easier for setting headers and handling errors.axios.post('/api/login', { email: 'user@example.com', password: 'secret' });
-
2. Display Blog Posts
Use Case: Show a list of blog posts on a homepage.
Request:
GET
from/api/posts
Best with:
fetch()
— simple and native.fetch('/api/posts') .then(res => res.json()) .then(setPosts);
-
3. Contact Us Form
Use Case: Submit a user's name, email, and message.
Request:
POST
to/api/contact
Best with:
fetch()
orAxios
fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, message }) });
-
4. Fetch Authenticated User Profile
Use Case: After login, fetch the logged-in user's data.
Request:
GET
/api/profile
with token in headersBest with:
Axios
axios.get('/api/profile', { headers: { Authorization: `Bearer ${token}` } });
-
5. Search Products
Use Case: A user types into a search bar and results update in real-time.
Request:
GET
from/api/products?query=phone
Best with:
Axios
— handles query params easily.axios.get('/api/products', { params: { query: 'phone' } });
-
6. Update User Profile
Use Case: A user updates their name or profile picture.
Request:
PUT
to/api/user/:id
Best with:
Axios
axios.put(`/api/user/${userId}`, { name: 'New Name', avatar: 'avatar.jpg' });
-
7. Load More Posts (Pagination)
Use Case: Fetch more data as the user scrolls down.
Request:
GET
from/api/posts?page=2
Best with:
Axios
orfetch()
axios.get('/api/posts', { params: { page: 2 } });
SWR / React Query (Caching & Pagination)
🧠 Detailed Explanation
When you're building a React app, you often need to get data from an API — like posts, products, users, or anything from your server.
Normally, you might use fetch()
or axios
in a useEffect()
and manage loading, error, and data states yourself.
That works... but it can get messy fast.
🌟 That’s where React Query
or SWR
comes in
These are smart tools that help you fetch and manage data automatically with:
- ✅ Caching – stores previously loaded data
- ✅ Auto re-fetching – gets fresh data in the background
- ✅ Error & loading handling – built-in, no need to write it manually
- ✅ Pagination & Infinite Scroll – easy to implement
In simple words: They help you write less code and build faster, smoother apps.
🔧 What is React Query?
React Query (also called TanStack Query) is a library that lets you:
- 📦 Fetch data with
useQuery()
oruseInfiniteQuery()
- 🔁 Keep previous data when paginating
- 🧠 Automatically cache results so you don’t re-fetch the same thing twice
const { data, isLoading, error } = useQuery(['posts'], () =>
fetch('/api/posts').then(res => res.json())
);
⚡ What is SWR?
SWR is a similar library by Vercel. It uses a strategy called Stale-While-Revalidate:
- 📄 Show cached (stale) data immediately
- 🔁 Then fetch new (fresh) data in the background
- 🚀 Gives you both speed and freshness
import useSWR from 'swr';
const fetcher = url => fetch(url).then(res => res.json());
const { data, error } = useSWR('/api/posts', fetcher);
🚀 Why use React Query or SWR instead of fetch()?
- ❌ With fetch(), you write loading, error, and caching logic yourself
- ✅ With React Query or SWR, they do it for you!
✨ Your app loads faster, feels smoother, and makes fewer requests.
📘 Summary:
- 🧠 React Query: Full-featured tool for REST or GraphQL — great for larger apps
- 🪶 SWR: Lightweight and great for fast GET requests
Both are awesome. Use whichever feels easier or fits your app best 💡
🛠️ Best Implementation (React Query)
📦 Goal:
Fetch paginated posts from /api/posts?page=1
, cache each page, and allow next/previous navigation without refetching already seen pages.
✅ Step 1: Setup React Query Provider
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<PostsList />
</QueryClientProvider>
);
}
✅ This wraps your app in a query provider — required to use hooks.
✅ Step 2: Create the Paginated Component
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
const fetchPosts = async (page) => {
const res = await fetch(`/api/posts?page=${page}`);
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
};
function PostsList() {
const [page, setPage] = useState(1);
const { data, isLoading, error, isFetching } = useQuery(
['posts', page], // cache key per page
() => fetchPosts(page), // fetch function
{ keepPreviousData: true } // ⬅️ keeps old data visible during loading
);
if (isLoading) return <p>Loading...</p>;
if (error) return <p style={{ color: 'red' }}>{error.message}</p>;
return (
<div>
<h3>Page {page}</h3>
<ul>
{data.posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
<div style={{ marginTop: '1rem' }}>
<button onClick={() => setPage(p => Math.max(p - 1, 1))} disabled={page === 1}>
Previous
</button>
<button onClick={() => setPage(p => p + 1)} disabled={!data.hasNextPage}>
Next
</button>
{isFetching && <span style={{ marginLeft: 10 }}>Updating...</span>}
</div>
</div>
);
}
✅ Features:
- `['posts', page]` ensures React Query caches per-page data
- `keepPreviousData: true` avoids flickering during loading
- `isFetching` shows background loading without hiding data
- `data.hasNextPage` can be returned from the API to manage end
📘 Example API Response:
{
"posts": [ { "id": 1, "title": "Post 1" }, ... ],
"hasNextPage": true
}
✅ Why This Is Best Practice
- ✅ Efficient: no refetch if page data is already cached
- ✅ UX-friendly: no flicker between pages
- ✅ Scalable: you can add filters or infinite scroll later
- ✅ Reusable: this pattern works for any paginated data
💡 Examples
📦 Scenario:
We want to show a list of blog posts from an API: /api/posts?page=1
. We will build it using both SWR and React Query.
🔹 Example 1: Using SWR
for data fetching
import useSWR from 'swr';
const fetcher = url => fetch(url).then(res => res.json());
function PostsWithSWR() {
const { data, error, isLoading } = useSWR('/api/posts?page=1', fetcher);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Failed to load</p>;
return (
<ul>
{data.posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
✅ SWR automatically:
- Caches this request
- Revalidates in the background
- Uses `fetcher` for consistent fetch logic
🔹 Example 2: Using React Query
with pagination
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
const fetchPosts = async (page) => {
const res = await fetch(`/api/posts?page=${page}`);
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
};
function PostsWithReactQuery() {
const [page, setPage] = useState(1);
const { data, isLoading, error, isFetching } = useQuery(
['posts', page],
() => fetchPosts(page),
{ keepPreviousData: true }
);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>{error.message}</p>;
return (
<div>
<h3>Posts (Page {page})</h3>
<ul>
{data.posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
<button onClick={() => setPage(p => Math.max(p - 1, 1))} disabled={page === 1}>
Previous
</button>
<button onClick={() => setPage(p => p + 1)} disabled={!data.hasNextPage}>
Next
</button>
{isFetching && <span> (updating...) </span>}
</div>
);
}
✅ React Query automatically:
- Caches each page individually
- Keeps previous page data on screen during loading
- Tracks loading with `isLoading` and `isFetching`
🔹 Example 3: React Query POST request (Create Post)
import { useMutation } from '@tanstack/react-query';
const createPost = async (post) => {
const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(post)
});
return res.json();
};
function NewPostForm() {
const mutation = useMutation({ mutationFn: createPost });
const handleSubmit = (e) => {
e.preventDefault();
mutation.mutate({ title: 'My Post', content: 'Hello world!' });
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Add Post</button>
{mutation.isPending && <p>Submitting...</p>}
{mutation.isSuccess && <p>Post added!</p>}
</form>
);
}
✅ `useMutation()` is perfect for sending POST, PUT, DELETE requests with form submission or button clicks.
🧠 Tip:
You can combine React Query pagination + mutation to update lists without a full page reload!
🔁 Alternatives
- Axios + useEffect – manual handling, no built-in caching
- Redux Toolkit Query – great for larger apps with Redux
- Relay – optimized for GraphQL APIs
❓ General Q&A
Q1: What is React Query?
A: React Query (now called TanStack Query) is a library that helps you fetch, cache, and update server data in your React apps. Instead of using fetch
or axios
inside useEffect
, React Query provides smart hooks like useQuery
and useMutation
that handle:
- ✔️ Loading states
- ✔️ Error handling
- ✔️ Caching and re-fetching
- ✔️ Pagination and background updates
It saves time and makes your data fetching code much cleaner and more powerful.
Q2: What is SWR?
A: SWR is a lightweight data fetching library created by Vercel (the team behind Next.js). It uses a strategy called Stale-While-Revalidate, which means:
- 📄 Show cached (stale) data immediately
- 🔁 In the background, re-fetch the latest data
This makes your app feel really fast while still keeping data fresh.
Q3: What’s the difference between React Query and SWR?
A:
- ⚙️ React Query is more powerful and includes features like mutations, pagination, devtools, and offline support.
- 🪶 SWR is more lightweight and best for simple, read-only data fetching (GET requests).
🔑 If you're building a big app with lots of features, use React Query. If you want something super lightweight for basic GETs, use SWR.
Q4: Can I use Axios with React Query or SWR?
A: Yes! Both libraries are compatible with Axios or Fetch. You just pass your Axios function as the fetcher:
// With SWR
const { data } = useSWR('/api/posts', url => axios.get(url).then(res => res.data));
// With React Query
useQuery(['posts'], () => axios.get('/api/posts').then(res => res.data));
✅ You can use whichever fetch tool you prefer.
Q5: What is caching and why does it matter?
A: Caching means storing data that you’ve already fetched so it doesn’t have to be re-fetched again. React Query and SWR automatically cache responses based on the query key (like ['posts', page]
).
Why it matters:
- ⚡ Faster user experience (no waiting again)
- 🔁 Fewer network requests
- 📦 Offline and low-data-friendly
Q6: Can I use SWR or React Query for pagination?
A:
- ✅ Yes, with React Query, pagination is built-in using
useQuery
andkeepPreviousData: true
. - ⚠️ With SWR, you can do pagination, but it requires manual logic or using
swr/infinite
.
Q7: Do I still need Redux if I use React Query?
A: Not for server data (like API posts, users, etc). React Query replaces the need for Redux in many cases. But you might still use Redux for local UI state (like modals, auth, or theme toggles).
✅ React Query is perfect for anything that lives on a server and is accessed via API.
🛠️ Technical Q&A
Q1: How do I fetch data with React Query?
A: Use the useQuery
hook. It automatically handles loading, error, and caching.
import { useQuery } from '@tanstack/react-query';
const { data, isLoading, error } = useQuery(['users'], () =>
fetch('/api/users').then(res => res.json())
);
✅ The key `['users']` is used for caching and revalidation.
Q2: How do I fetch data with SWR?
A: Use the useSWR
hook with a fetcher function.
import useSWR from 'swr';
const fetcher = url => fetch(url).then(res => res.json());
const { data, error, isLoading } = useSWR('/api/users', fetcher);
✅ SWR returns cached data first, then updates it in the background.
Q3: How do I implement pagination with React Query?
A: Include the page number in the query key. Use keepPreviousData
to prevent flicker.
const [page, setPage] = useState(1);
const { data, isFetching } = useQuery(['posts', page], () =>
fetch(`/api/posts?page=${page}`).then(res => res.json()),
{ keepPreviousData: true }
);
✅ Each page is cached separately. Users can go back without refetching.
Q4: How do I send POST/PUT requests with React Query?
A: Use the useMutation
hook.
const mutation = useMutation({
mutationFn: (newPost) =>
fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost)
}).then(res => res.json())
});
mutation.mutate({ title: 'New post' });
✅ `mutation.mutate()` triggers the request and provides success/error states.
Q5: How do I refetch data after a mutation?
A: Call queryClient.invalidateQueries()
to tell React Query to refetch data.
import { useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
useMutation({
mutationFn: createPost,
onSuccess: () => {
queryClient.invalidateQueries(['posts']);
}
});
✅ Keeps the UI in sync with backend changes automatically.
Q6: Can I prefetch data with React Query?
A: Yes! Use queryClient.prefetchQuery()
— useful for hover previews or next pages.
queryClient.prefetchQuery(['posts', 2], () =>
fetch('/api/posts?page=2').then(res => res.json())
);
✅ Boosts performance by loading data in the background before it’s needed.
Q7: How do I refresh SWR data manually?
A: Use the mutate()
function.
import useSWR, { mutate } from 'swr';
const { data } = useSWR('/api/posts', fetcher);
// Manually revalidate
const refresh = () => mutate('/api/posts');
✅ Great for refreshing after a form submission or event.
✅ Best Practices
✅ 1. Use descriptive query keys (React Query)
Why? The key determines caching. A unique key like ['posts', page]
helps React Query cache each page separately.
useQuery(['posts', page], () => fetchPosts(page));
✅ If the key changes, it refetches. If not, it uses the cache.
✅ 2. Use keepPreviousData: true
(React Query)
Why? This prevents the UI from going blank while loading the next page.
useQuery(['posts', page], () => fetchPosts(page), {
keepPreviousData: true
});
✅ Makes pagination UX much smoother.
✅ 3. Reuse a fetcher for SWR or React Query
Why? Keeps your code DRY and easier to maintain.
// api.js
export const fetcher = url => fetch(url).then(res => res.json());
// component
const { data } = useSWR('/api/posts', fetcher);
✅ Can replace fetch with axios or add auth headers in one place.
✅ 4. Use React Query Devtools
Why? Helps you see which queries are loading, cached, or failing.
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
✅ Useful for debugging performance and caching.
✅ 5. Invalidate or update the cache after POST/PUT/DELETE
Why? So your UI stays in sync with the backend.
const queryClient = useQueryClient();
const mutation = useMutation(addPost, {
onSuccess: () => {
queryClient.invalidateQueries(['posts']);
}
});
✅ Or use queryClient.setQueryData()
for instant updates.
✅ 6. Trust the cache when possible
Why? React Query stores fresh data — avoid unnecessary re-fetches.
useQuery(['user'], fetchUser, {
staleTime: 5 * 60 * 1000 // 5 minutes
});
✅ This keeps the data fresh without hammering your API.
✅ 7. Show feedback on mutation success/failure
Why? Good UX = visible feedback.
const mutation = useMutation(saveForm, {
onSuccess: () => alert('Saved!'),
onError: (err) => alert('Failed: ' + err.message)
});
✅ Show loaders, toasts, or inline messages to help users.
🌍 Real-World Examples
-
1. Blog Posts Feed
Use Case: Load a list of articles from
/api/posts
.Best Tool:
React Query
orSWR
Why? Automatically handles loading, caching, and revalidation. If the user navigates back to the page, the list is already cached and instantly displayed.
-
2. Paginated Product Catalog
Use Case: Display products page by page with “Next/Previous” buttons.
Best Tool:
React Query
withkeepPreviousData: true
Why? Prevents flicker during page changes and caches every page of data.
-
3. Fetch Logged-in User Profile
Use Case: Show user info after login at
/api/me
.Best Tool:
SWR
Why? SWR quickly shows cached user data while it silently updates with the latest info from the server.
-
4. Real-Time Stock Prices
Use Case: Display constantly updating stock prices every 10 seconds.
Best Tool:
SWR
withrefreshInterval
Why? Easy polling without manually setting intervals.
useSWR('/api/stocks', fetcher, { refreshInterval: 10000 });
-
5. Infinite Scroll Order History
Use Case: Show customer orders using scroll pagination.
Best Tool:
React Query
withuseInfiniteQuery()
Why? Built-in support for cursor-based pagination with smooth scroll loading.
-
6. Optimistic Comment Submission
Use Case: Post a comment and show it instantly before server confirms.
Best Tool:
React Query
withuseMutation()
andonMutate
Why? Makes the app feel faster and more interactive with optimistic updates.
-
7. Admin Dashboard KPIs
Use Case: Show total users, sales, and charts in an admin panel.
Best Tool:
SWR
orReact Query
Why? Shows cached data immediately, then updates in background. Smooth UX for dashboards with metrics that don’t change every second.
Handling Loading, Error & Success States in React
🧠 Detailed Explanation
When your React app talks to a server — like to load posts, products, or user info — that request takes time.
During that time, your app is in one of three common states:
- ⏳ Loading: You're waiting for data to arrive.
- ❌ Error: Something went wrong (like no internet or server crash).
- ✅ Success: The data arrived and can now be shown.
👉 These states help make your app more reliable and user-friendly.
💡 Why are these states important?
- ✔️ Users know the app is working (not frozen)
- ✔️ They get clear feedback if something fails
- ✔️ Data appears only when it's ready — no blank screens or crashes
🛠️ What does a basic pattern look like?
You use three pieces of state in your component:
loading
– to show a spinner or messageerror
– to show what went wrongdata
– to show the actual content
if (loading) return <p>Loading...</p>;
if (error) return <p>Something went wrong: {error.message}</p>;
return <div>Hello, {data.name}</div>;
✅ This logic ensures your app reacts correctly to real-world situations.
🔁 Example Timeline:
- ⏱ 0s → Component mounts → Loading starts
- 📡 1s → Server responds → Success (or Error)
- 🎯 1.2s → App shows the final content
🚀 Real-World Example:
Imagine opening an Instagram profile:
- 📶 While data loads → Show spinner or skeletons
- 😓 If API fails → Show "Could not load profile"
- 🎉 If successful → Show user info, photos, and buttons
✨ This pattern gives your app real polish and professionalism.
🛠️ Best Implementation
🎯 Goal:
We want to fetch data from an API, display a loading spinner while it loads, show an error message if it fails, and display the data if successful.
✅ Step-by-Step Code
import { useEffect, useState } from 'react';
function PostViewer() {
const [data, setData] = useState(null); // Holds fetched data
const [loading, setLoading] = useState(true); // Indicates loading
const [error, setError] = useState(null); // Stores error if any
useEffect(() => {
const fetchPost = async () => {
try {
const response = await fetch('/api/posts/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchPost();
}, []);
if (loading) {
return <p>⏳ Loading post...</p>;
}
if (error) {
return <p style={{ color: 'red' }}>❌ Error: {error.message}</p>;
}
return (
<div>
<h2>✅ Post Loaded Successfully</h2>
<h3>{data.title}</h3>
<p>{data.body}</p>
</div>
);
}
🧠 What’s Happening:
useState()
is used to track data, loading, and error individually.useEffect()
triggers the fetch on component mount.- Loading state is true until the fetch completes.
- Error state is set if the fetch fails (e.g., network issue, 404).
- Data state is set if the fetch is successful and the response is valid.
✅ Bonus Tips:
- Wrap the fetch in
try/catch
to handle network or parsing errors. - Use
finally
to ensure loading is always set to false after attempt. - Return early in the component using
if
checks to show proper UI quickly. - Show custom loading indicators (spinner or skeleton UI) for better UX.
- Display clear, user-friendly error messages (not just raw JSON errors).
📘 Real API Response Example (Mock):
{
"id": 1,
"title": "React Async States Made Simple",
"body": "This is the content of the blog post."
}
🚀 Where to Use This Pattern:
- ✔️ Product detail pages
- ✔️ User profile pages
- ✔️ Dashboards pulling data from APIs
- ✔️ Any CRUD operation with async fetch
💡 Examples
🎯 Goal:
We want to fetch a post from an API, show a loading message while it loads, show an error if it fails, and display the post if it succeeds.
🔹 Example 1: Using Fetch + useEffect + useState
import React, { useEffect, useState } from 'react';
function PostViewer() {
const [data, setData] = useState(null); // Holds post data
const [loading, setLoading] = useState(true); // True when fetching
const [error, setError] = useState(null); // Holds error info
useEffect(() => {
const fetchPost = async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
if (!res.ok) throw new Error('Failed to fetch');
const post = await res.json();
setData(post);
} catch (err) {
setError(err);
} finally {
setLoading(false); // Done fetching
}
};
fetchPost();
}, []);
if (loading) return <p>⏳ Loading post...</p>;
if (error) return <p style={{ color: 'red' }}>❌ {error.message}</p>;
return (
<div>
<h3>✅ {data.title}</h3>
<p>{data.body}</p>
</div>
);
}
🧠 What's happening:
- ⏳ Starts with
loading: true
- 🎯 Fetches data on component mount
- ✅ On success, shows data
- ❌ On failure, shows error message
🔹 Example 2: Reusable Custom Hook for Async State
Let’s make a custom hook that works with any URL.
// useFetch.js
import { useState, useEffect } from 'react';
export function useFetch(url) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url);
if (!res.ok) throw new Error('Failed to fetch');
const result = await res.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// PostViewer.js
import { useFetch } from './useFetch';
function PostViewer() {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts/1');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h3>{data.title}</h3>
<p>{data.body}</p>
</div>
);
}
✅ Benefit: Cleaner components, reusable logic across your app.
🔹 Example 3: Using React Query (Alternative)
import { useQuery } from '@tanstack/react-query';
function PostWithReactQuery() {
const { data, isLoading, error } = useQuery(['post'], () =>
fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => res.json())
);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error loading post</p>;
return (
<div>
<h2>{data.title}</h2>
<p>{data.body}</p>
</div>
);
}
✨ React Query handles loading, error, and success for you.
🔁 Alternative Methods
- Use libraries like React Query or SWR which handle states automatically
- Use custom hooks like
useAsync()
to abstract this logic
❓ General Q&A
Q1: What are loading, error, and success states?
A: These are the three most common "statuses" of any async request in a React app (like fetching data from an API).
- Loading means: "We're waiting for the data."
- Error means: "Something went wrong (e.g., no internet, 404)."
- Success means: "Data is here and ready to display."
Q2: Why is handling these states important?
A: Because users hate being confused. Without clear feedback, they might think your app is broken or slow.
- 🧭 Loading tells them something is happening
- 💥 Error explains why something failed
- 🎯 Success confirms the action or shows the content
Q3: Do I need to manually track these states?
A: It depends:
- 🛠️ If you're using plain
fetch()
oraxios
, yes — you’ll need to useuseState
to trackloading
,error
, anddata
. - ⚡ If you use libraries like
React Query
orSWR
, they handle these states automatically for you.
Q4: What is the difference between isLoading
and isFetching
in React Query?
A:
isLoading
istrue
only on the first load (when no data is cached).isFetching
istrue
any time a request is in progress, even background refetches.
isLoading
for the initial loader, and isFetching
to show subtle "refreshing…" indicators.
Q5: What should I show when loading?
A: Show any of the following:
- 🔄 Spinners (CSS or SVG)
- 🦴 Skeleton loaders (gray boxes to simulate content)
- ⌛ Simple “Loading...” text
Q6: What makes a good error message?
A: A good error message is:
- ✔️ Clear (“Something went wrong loading this post”)
- ✔️ Actionable (“Please check your connection.”)
- ✔️ Optional: Log the full error to console for developers
Q7: What if my data is null but there's no error?
A: This usually means the request worked, but the server returned no content. Handle it like a “No Data” state:
if (!loading && !error && !data) {
return <p>No results found.</p>;
}
✅ Always check for empty or null data, especially with search or filters.
🛠️ Technical Q&A
Q1: How do I manage loading, error, and success states manually in React?
A: Use useState
to track each state individually. This is the manual approach when using fetch
or axios
.
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => {
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
})
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
✅ This ensures your component responds correctly in every async scenario.
Q2: How do I handle async states in React Query?
A: Use useQuery
— it automatically provides isLoading
, error
, and data
.
import { useQuery } from '@tanstack/react-query';
const { data, error, isLoading } = useQuery(['posts'], () =>
fetch('/api/posts').then(res => res.json())
);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <div>{data.title}</div>;
✅ Much cleaner than tracking all states manually.
Q3: What’s the difference between error
and !res.ok
?
A:
error
is a state you define in React (e.g.,useState(null)
).res.ok
is a flag in the Fetch API response object — it'sfalse
if the HTTP status is 400–599.
const res = await fetch('/api/data');
if (!res.ok) throw new Error('Server returned an error');
✅ Always check res.ok
to catch non-network errors like 404 or 500.
Q4: How do I show a loading skeleton instead of a spinner?
A: Use placeholder content that looks like the final layout, but grayed out.
if (loading) {
return <div className="skeleton-box"></div>;
}
✅ Better UX — reduces perceived wait time.
Q5: How do I handle retry logic on failure?
A: With manual fetch, you can wrap retries in a loop or setTimeout. React Query has built-in retry behavior.
// React Query retry logic
useQuery(['data'], fetchData, {
retry: 3, // retry 3 times before giving up
retryDelay: attemptIndex => attemptIndex * 1000 // delay between retries
});
✅ Helps with flaky networks and temporary errors.
Q6: What if I only want to show the error after the loading ends?
A: Check !loading && error
to avoid showing error flashes during fetch start.
if (loading) return <p>Loading...</p>;
if (!loading && error) return <p>Error occurred</p>;
✅ Prevents unnecessary flicker or false errors on component mount.
Q7: How can I centralize loading/error handling in a reusable component?
A: Create a wrapper component or custom hook like:
// AsyncWrapper.js
function AsyncWrapper({ loading, error, children }) {
if (loading) return <p>Loading...</p>;
if (error) return <p style={{ color: 'red' }}>Error: {error.message}</p>;
return <>{children}</>;
}
// Usage
<AsyncWrapper loading={loading} error={error}>
<h2>{data.title}</h2>
</AsyncWrapper>
✅ Keeps logic clean and reusable across multiple pages or views.
✅ Best Practices
1. Always show something during loading
Why? Users should know your app is working, not frozen.
if (loading) return <p>Loading...</p>;
✅ Use a spinner, loader, or skeleton. Never leave the screen blank.
2. Always handle errors gracefully
Why? Users should never see a crash or raw error object.
if (error) return <p style={{ color: 'red' }}>Something went wrong: {error.message}</p>;
✅ You can also show a retry button or “Try again” message.
3. Show actual content only after data is ready
Why? Prevents errors like trying to access data.title
when data
is null.
if (!loading && !error && data) {
return <div>{data.title}</div>;
}
✅ Always wrap content inside conditionals.
4. Use finally()
or state cleanup to reset loading
Why? Avoids loading state getting stuck forever if an error happens.
fetch('/api')
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
✅ Always update loading after success OR error.
5. Use loading indicators that match the UI
Why? Users expect loading to look like the thing it’s loading.
- 📊 Loading a chart? Show empty chart placeholders
- 📷 Loading a profile? Show circle + name skeleton
6. Centralize state handling in a hook or wrapper
Why? Avoid repeating the same loading/error logic everywhere.
// AsyncWrapper.js
function AsyncWrapper({ loading, error, children }) {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <>{children}</>;
}
✅ Keeps components clean and consistent.
7. Use React Query or SWR to simplify state tracking
Why? These libraries provide isLoading
, error
, and data
automatically.
const { data, isLoading, error } = useQuery(['post'], fetchPost);
✅ Saves you from writing boilerplate state logic manually.
🌍 Real-World Examples
-
1. User Profile Page
Use Case: Display the logged-in user’s profile after authentication.
Flow:
- ⏳ Loading: Show skeleton avatar and name
- ✅ Success: Show actual user profile info
- ❌ Error: Show “Failed to load profile” with retry button
-
2. E-commerce Product Page
Use Case: Load product details when a user clicks a product card.
Flow:
- ⏳ Loading: Show image placeholder and text shimmer
- ✅ Success: Render product image, price, description
- ❌ Error: Show fallback message or 404
-
3. Comments Section (Blog or News)
Use Case: Fetch and display comments for an article.
Flow:
- ⏳ Loading: Show “Loading comments…” or spinner
- ✅ Success: Show list of comments
- ❌ Error: Show “Could not load comments”
-
4. Submit Contact Form
Use Case: Submit form data to the server.
Flow:
- ⏳ Loading: Disable form and show “Sending…”
- ✅ Success: Show “Thank you!” or redirect
- ❌ Error: Show validation message or "Failed to submit"
-
5. Infinite Scroll Feed (Social App)
Use Case: Load more posts as user scrolls.
Flow:
- ⏳ Loading: Show post skeletons as next page loads
- ✅ Success: Add posts to list
- ❌ Error: Show “Couldn’t load more posts” at bottom
-
6. Admin Dashboard Stats
Use Case: Show sales, user count, and KPIs from an API.
Flow:
- ⏳ Loading: Show animated metric boxes (skeleton)
- ✅ Success: Render charts/numbers
- ❌ Error: Show “Data unavailable” for each widget
-
7. Weather or Geo App
Use Case: Get weather based on user's location.
Flow:
- ⏳ Loading: Show “Detecting location…” message
- ✅ Success: Show weather and city
- ❌ Error: Show “Location not found” or “Permission denied”
Debouncing & Throttling Inputs in React
🧠 Detailed Explanation
When users type in a search box, scroll, or resize the window, your app may run functions too often — like calling an API on every key press or updating state 60 times per second during scroll.
That’s where Debounce and Throttle come in.
⚡ What is Debounce
?
Debounce means: “Wait for a pause before doing something.”
Example: In a search bar, if the user types a
, ab
, abc
quickly — the search runs only after they stop typing for 500ms.
🧠 Use debounce when:
- 🕵️ You want to search only after typing stops
- 🔍 You're sending API calls from input
- 📅 You want to wait before submitting
🚀 What is Throttle
?
Throttle means: “Allow only one action every X milliseconds.”
Example: On scroll, throttle lets the handler run only once per second — not every pixel of scroll.
🧠 Use throttle when:
- 🖱️ Handling scroll events
- 📏 Window resizing
- 🎮 Dragging, zooming, or continuous events
🔁 How Are They Different?
- Debounce: Waits for silence (pause in input)
- Throttle: Runs every fixed time (limits how often)
💡 Debounce = wait, then fire once
💡 Throttle = fire at intervals
💬 Everyday Examples:
- 🔍 Debounce: Search box (wait until done typing)
- 🧭 Throttle: Map pan or scroll (limit frequency)
Both improve performance and reduce unnecessary work in your app!
🛠️ Best Implementation
🎯 Goal:
We want to create an input field (like a search bar) where:
- ✅ Debounce: The search function is called only after the user stops typing for 500ms.
- ✅ Throttle: A version that allows one input call every fixed interval (e.g., scroll-based events).
✅ Step 1: Create a useDebounce
custom hook
// useDebounce.js
import { useEffect, useState } from 'react';
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer); // cleanup on unmount/change
}, [value, delay]);
return debouncedValue;
}
✅ This hook returns the debouncedValue
only after the user stops typing for delay
ms.
✅ Step 2: Use it in a Search Component
import { useState, useEffect } from 'react';
import { useDebounce } from './useDebounce';
function SearchBar() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 500); // delay of 500ms
useEffect(() => {
if (debouncedQuery) {
console.log('Fetching results for:', debouncedQuery);
// Imagine this is your API call
}
}, [debouncedQuery]);
return (
<input
type="text"
placeholder="Search..."
value={query}
onChange={e => setQuery(e.target.value)}
/>
);
}
✅ This approach ensures no API call happens during fast typing — only after a pause.
✅ Step 3: Create a throttle
utility function (for scroll/resize)
// utils/throttle.js
export function throttle(fn, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
✅ Useful when you want to prevent events from firing too often (e.g., scroll, resize).
✅ Step 4: Use throttle in an event listener
import { useEffect } from 'react';
import { throttle } from './utils/throttle';
function ScrollTracker() {
useEffect(() => {
const handleScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 1000); // 1 second throttle
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <p>Scroll to see console logs every second.</p>;
}
✅ Reduces performance load from scroll events.
📘 Summary of When to Use Each
Use Case | Use Debounce | Use Throttle |
---|---|---|
Search input | ✅ Yes | ❌ No |
Window resize | ❌ No | ✅ Yes |
Form filters | ✅ Yes | ❌ No |
Scroll-based events | ❌ No | ✅ Yes |
🔐 Bonus Best Practices:
- 💡 Always cancel timers on unmount to avoid memory leaks.
- 💡 Debounce user inputs for cleaner and cheaper API requests.
- 💡 Throttle heavy DOM events (scroll, drag, resize) to improve performance.
- 💡 Use libraries like
lodash
if you need stable, tested utilities.
💡 Examples (With Clear Code & Explanation)
🔹 Example 1: Debouncing a Search Input
Goal: Don’t call the API while the user is still typing — only after they pause for 500ms.
// useDebounce.js
import { useState, useEffect } from 'react';
export function useDebounce(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebounced(value);
}, delay);
return () => clearTimeout(timer); // cleanup timer on change
}, [value, delay]);
return debounced;
}
// SearchBar.js
import { useState, useEffect } from 'react';
import { useDebounce } from './useDebounce';
function SearchBar() {
const [input, setInput] = useState('');
const debouncedInput = useDebounce(input, 500);
useEffect(() => {
if (debouncedInput) {
console.log('Searching for:', debouncedInput);
// fetch(`/api/search?q=${debouncedInput}`)
}
}, [debouncedInput]);
return (
<input
value={input}
onChange={e => setInput(e.target.value)}
placeholder="Search..."
/>
);
}
✅ This waits for 500ms of no typing before firing.
🔹 Example 2: Throttling a Scroll Event
Goal: Log scroll position once every 1000ms — no matter how fast the user scrolls.
// throttle.js
export function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// ScrollTracker.js
import { useEffect } from 'react';
import { throttle } from './throttle';
function ScrollTracker() {
useEffect(() => {
const handleScroll = throttle(() => {
console.log('Scroll Y:', window.scrollY);
}, 1000);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <p>Scroll the page and check the console every second.</p>;
}
✅ Now you’re not overwhelming the browser with too many logs or updates.
🔹 Example 3: Debouncing Form Filters (Live)
Goal: Debounce user input in a product filter form so API calls are only made after delay.
function ProductFilter() {
const [price, setPrice] = useState('');
const debouncedPrice = useDebounce(price, 300);
useEffect(() => {
if (debouncedPrice) {
console.log('Filtering price over:', debouncedPrice);
// fetch data here
}
}, [debouncedPrice]);
return (
<input
type="number"
placeholder="Price above"
value={price}
onChange={e => setPrice(e.target.value)}
/>
);
}
✅ Great for range sliders, filter forms, and search suggestions.
🔹 Example 4: Throttle Window Resize Event
Goal: Print window width at most once every second during resizing.
useEffect(() => {
const handleResize = throttle(() => {
console.log('Window width:', window.innerWidth);
}, 1000);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
✅ Prevents layout recalculations and re-renders happening too often.
🔁 Alternatives
- Use Lodash:
_.debounce
and_.throttle
- React-specific libraries like
use-debounce
- Custom reusable hooks (e.g.,
useDebounce
)
❓ General Q&A
Q1: What is debouncing in simple terms?
A: Debouncing means: "Wait a little while before running a function." If the user keeps triggering the event (like typing), the function will keep waiting. It only runs after they stop for a certain time (e.g., 500ms).
🧠 Use Case: A search input where you want to call the API only after the user finishes typing.
💡 Real Life Example: A pause after talking before someone else answers. If you keep talking, the other person waits.
Q2: What is throttling in simple terms?
A: Throttling means: "Allow a function to run only once in a set time window." Even if you keep triggering it (like scrolling), it will run once per interval (e.g., every 1 second).
🧠 Use Case: Scroll events, where too many calls would slow down the app.
💡 Real Life Example: A camera taking one picture every second, even if the button is pressed repeatedly.
Q3: Why should I use debounce in a React input field?
A: Because without it, the function (e.g., search API) runs on every keystroke — which is wasteful and can slow down the app or server.
✅ Debounce ensures the function runs only when the user pauses typing.
👎 Without debounce: 10 characters = 10 API calls
👍 With debounce: 10 characters = 1 API call after pause
Q4: Which one should I use — debounce or throttle?
A:
- ✔️ Use debounce when the final value matters (e.g., search input, filter)
- ✔️ Use throttle when the ongoing action matters (e.g., scroll, resize)
Q5: Can I debounce or throttle in React without a library?
A: Yes! You can use plain JavaScript with setTimeout
for debounce and setTimeout + flag
for throttle.
✅ For more complex control, you can use Lodash's _.debounce
and _.throttle
, or write custom hooks like useDebounce
.
Q6: What happens if I forget to clear the debounce timer?
A: You may get memory leaks or unwanted function calls even after the component is unmounted. Always clear the timeout in the cleanup function inside useEffect()
.
useEffect(() => {
const timer = setTimeout(() => doSomething(), 300);
return () => clearTimeout(timer); // cleanup
}, [value]);
Q7: Can I throttle API calls?
A: Yes, but it depends on the situation. If the API endpoint allows repeated calls (like tracking scroll), you can throttle it. But for inputs or filters, debounce is safer so you don’t waste requests on in-progress values.
🛠️ Technical Q&A
Q1: How do I debounce an input in React without a library?
A: Use a custom hook with useEffect
and setTimeout
to wait until typing stops before running the function.
// useDebounce.js
import { useEffect, useState } from 'react';
export function useDebounce(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer); // clear timer on value change or unmount
}, [value, delay]);
return debounced;
}
💡 Use it like this:
const debouncedSearch = useDebounce(searchTerm, 500);
✅ This ensures your API only triggers once after the user stops typing.
Q2: How do I throttle a function in React?
A: Use a utility function that blocks the call if it happened too recently.
// throttle.js
export function throttle(fn, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
💡 Usage:
const throttledScroll = throttle(() => {
console.log(window.scrollY);
}, 1000);
✅ Now, it logs scroll position only once every second, no matter how fast you scroll.
Q3: Can I debounce inside an event handler?
A: Yes, but you need to memoize the function using useCallback
so it doesn’t reset on every render.
const handleChange = useCallback(
debounce((e) => {
console.log('API call for:', e.target.value);
}, 300),
[]
);
✅ This ensures stable debounce behavior across renders.
Q4: What’s the main difference between debounce
and throttle
in timing behavior?
A:
- Debounce: Fires only once after the last event (great for inputs).
- Throttle: Fires at fixed intervals during activity (great for scroll).
// Debounce (wait for 300ms pause)
searchInput.onChange = debounce(search, 300);
// Throttle (call at most every 1s)
window.onscroll = throttle(logScroll, 1000);
✅ Choose based on the type of activity you're managing.
Q5: What happens if I forget to clean up debounce timers?
A: It can cause memory leaks or run setTimeout after the component is unmounted.
✅ Always use:
useEffect(() => {
const timer = setTimeout(...);
return () => clearTimeout(timer); // Cleanup!
}, []);
🧠 This ensures safe and predictable behavior.
Q6: Can I combine debounce and throttle?
A: Not directly, but you can throttle some parts (like scrolling) and debounce others (like fetching). It's best to apply each where it fits — not both to the same function unless you wrap intelligently.
Tip: Lodash allows chaining with _.throttle(_.debounce(fn))
but use with care.
✅ Best Practices
1. Use debounce for text inputs like search, filters, and form validation
Why? You don’t want to make an API call every time the user types a letter. Debounce waits until typing stops.
const debouncedQuery = useDebounce(searchInput, 500);
useEffect(() => {
if (debouncedQuery) {
fetch(`/api/search?q=${debouncedQuery}`);
}
}, [debouncedQuery]);
✅ Clean, efficient, user-friendly searching.
2. Use throttle for scroll, resize, and drag events
Why? These events fire rapidly — throttling limits how often your function runs.
const throttledScroll = throttle(() => {
console.log('User scrolled:', window.scrollY);
}, 1000);
useEffect(() => {
window.addEventListener('scroll', throttledScroll);
return () => window.removeEventListener('scroll', throttledScroll);
}, []);
✅ Better performance with less CPU usage.
3. Always clear timeout or throttle timers in useEffect
cleanup
Why? Prevents memory leaks and unwanted side effects if the component unmounts early.
useEffect(() => {
const timer = setTimeout(...);
return () => clearTimeout(timer);
}, []);
✅ Keeps your component behavior predictable.
4. Wrap debounce/throttle functions in useCallback()
when used in event handlers
Why? Prevents re-creating the debounced function on every render.
const handleChange = useCallback(
debounce((value) => {
console.log('Input:', value);
}, 300),
[]
);
✅ Stable and performant input handlers.
5. Create reusable hooks like useDebounce
or useThrottle
Why? Keeps logic clean, centralized, and DRY (Don't Repeat Yourself).
// useDebounce(value, delay)
const debouncedValue = useDebounce(inputValue, 500);
✅ One-liner logic in components, better separation of concerns.
6. Use Lodash if you want bulletproof solutions
Why? Lodash has production-tested versions of debounce
and throttle
.
import { debounce } from 'lodash';
const handleChange = debounce((e) => {
console.log(e.target.value);
}, 300);
✅ Use Lodash when working on large-scale or critical apps.
7. Keep debounce/throttle delay values appropriate
Why? Too short defeats the purpose, too long feels slow.
- ⌨️ Inputs: 300–500ms
- 🖱️ Scroll/Resize: 1000ms
🌍 Real-World Examples
-
1. Live Search Input (Debounce)
Use Case: User types into a search box to find articles or products.
Problem: Without debounce, every keystroke triggers a request (wastes bandwidth & server resources).
Solution: Debounce the input with 300–500ms so the API runs only after typing stops.
-
2. Filtering a Product List (Debounce)
Use Case: User adjusts price range or category filters in a sidebar.
Problem: Changing multiple filters rapidly sends multiple requests.
Solution: Debounce filter inputs to wait for pause before fetching new data.
-
3. Infinite Scroll Feed (Throttle)
Use Case: Auto-load new content as the user scrolls to the bottom.
Problem: Scroll events fire continuously, and can overload your handler.
Solution: Throttle the scroll listener to check position once every second.
-
4. Resizing a Window with a Dynamic Chart (Throttle)
Use Case: A chart should resize to fit the window width.
Problem:
resize
event fires dozens of times per second.Solution: Throttle the resize event to only update the chart every 1s.
-
5. Auto-Save Draft Content (Debounce)
Use Case: User writes in a blog editor, and the system saves drafts in real-time.
Problem: Saving every character is wasteful.
Solution: Debounce the auto-save (e.g., 2 seconds after last change).
-
6. Button Click to Trigger Expensive API (Throttle)
Use Case: User taps a “refresh” button repeatedly.
Problem: Multiple clicks trigger duplicate requests.
Solution: Throttle the click handler to allow one click per second.
-
7. Map Panning or Zooming (Throttle)
Use Case: Displaying nearby locations on a draggable map.
Problem: Calling the API every few pixels of movement is too frequent.
Solution: Throttle the map event so data is fetched only once every few hundred milliseconds.
Learn more about React setup