practice for PW sde 1 frontend interview

##Different datatypes in JavaScript##

In JavaScript, data types are mainly divided into Primitive and Non-Primitive (Reference) types.


Primitive Types:

1. Number: Represents both integer and floating-point numbers.

2. String: Represents a sequence of characters.

3. Boolean: Represents true or false.

4. Undefined: Represents a variable that has been declared but not assigned a value.

5. Null: Represents the intentional absence of any object value.

6. Symbol: Represents a unique and immutable value that can be used as an object property key.

7. BigInt: Represents integers with arbitrary precision.


Non-Primitive (Reference) Types:

1. Object: Represents a collection of properties.

2. Array: Represents an ordered collection of values.

3. Function: Represents a callable object.


##Difference between var, let, and const##


VAR IN JAVASCRIPT


1. Function Scoped

Variables declared with var are function-scoped, not block-scoped.

This means:

- Accessible anywhere inside the function

- If declared outside a function, it becomes global


Example (Function Scope):

function test() {

var x = 10;

console.log(x); // 10

}

test();

console.log(x); // ReferenceError


Not Block Scoped:

var ignores block scope like if, for, and curly braces

if (true) {

var y = 20;

}

console.log(y); // 20 (accessible outside block)


This is a common interview question.


2. Hoisting

var declarations are hoisted to the top of their function scope.

Only the declaration is hoisted, not the initialization.


Example:

console.log(a); // undefined

var a = 5;


JavaScript internally treats it like:

var a; // hoisted

console.log(a); // undefined

a = 5;


That's why it prints undefined instead of throwing an error.


3. Can Be Re-declared

var allows redeclaration in the same scope.


var name = "Shamim";

var name = "Akhter";

console.log(name); // "Akhter"


This can cause bugs in large applications.


4. Adds Property to Global Object

If declared globally, var attaches to the window object in browser.


var age = 25;

console.log(window.age); // 25 (in browser)


But let and const do NOT do this.


5. Classic var Loop Problem (Interview Favorite)

for (var i = 0; i < 3; i++) {

setTimeout(() => {

console.log(i);

}, 1000);

}


Output:

3

3

3


Because:

- var is function-scoped

- All callbacks share the same i

- After loop ends, i equals 3


LET IN JAVASCRIPT


1. Block Scoped

Variables declared with let are block-scoped.

They are only accessible within the block they are declared in.


Example:

if (true) {

let x = 10;

console.log(x); // 10

}

console.log(x); // ReferenceError


2. Hoisting with Temporal Dead Zone

let is hoisted but not initialized.

Accessing it before declaration results in ReferenceError.


Example:

console.log(a); // ReferenceError

let a = 5;


This is called the Temporal Dead Zone (TDZ).


3. Cannot Be Re-declared

let does not allow redeclaration in the same scope.


let name = "Shamim";

let name = "Akhter"; // SyntaxError


4. Can Be Reassigned

let allows reassignment of values.


let age = 25;

age = 30; // Valid

console.log(age); // 30


5. Does Not Add to Global Object

Even if declared globally, let does not attach to window object.


let age = 25;

console.log(window.age); // undefined


CONST IN JAVASCRIPT


1. Block Scoped

Like let, const is also block-scoped.


Example:

if (true) {

const x = 10;

console.log(x); // 10

}

console.log(x); // ReferenceError


2. Hoisting with Temporal Dead Zone

const is hoisted but not initialized.

Accessing it before declaration results in ReferenceError.


Example:

console.log(a); // ReferenceError

const a = 5;


3. Cannot Be Re-declared

const does not allow redeclaration in the same scope.


const name = "Shamim";

const name = "Akhter"; // SyntaxError


4. Cannot Be Reassigned

const does not allow reassignment.


const age = 25;

age = 30; // TypeError: Assignment to constant variable


However, for objects and arrays, the properties can be modified:

const person = { name: "Shamim" };

person.name = "Akhter"; // Valid

person.age = 25; // Valid


const arr = [1, 2, 3];

arr.push(4); // Valid

arr = [5, 6]; // TypeError


5. Must Be Initialized

const must be initialized at the time of declaration.


const x; // SyntaxError: Missing initializer

const y = 10; // Valid


6. Does Not Add to Global Object

Even if declared globally, const does not attach to window object.


const age = 25;

console.log(window.age); // undefined


WHY var IS AVOIDED TODAY


Problems with var:

- No block scope

- Hoisting confusion

- Allows redeclaration

- Loop closure issues


Modern JavaScript prefers:

- let for variables that need to be reassigned

- const for variables that should not be reassigned


COMPARISON TABLE


Feature | var | let | const

---------------------------|------------------|------------------|------------------

Scope | Function | Block | Block

Hoisting | Yes (undefined) | Yes (TDZ) | Yes (TDZ)

Temporal Dead Zone | No | Yes | Yes

Re-declaration | Allowed | Not Allowed | Not Allowed

Re-assignment | Allowed | Allowed | Not Allowed

Initialization Required | No | No | Yes

Global Object Property | Yes | No | No

Best Use Case | Avoid | Mutable values | Immutable values


###Temporal Dead Zone###


Temporal Dead Zone (TDZ) is a behavior in JavaScript that occurs when you try to access a variable before it has been declared.

accessing a let or const variable before it's declared results in a ReferenceError.

for example:

console.log(x); // ReferenceError

let x = 5;

###Normal vs Arrow functions (syntax + this behavior)###


NORMAL FUNCTIONS


1. Syntax

Normal functions are declared using the function keyword.

They can be defined in multiple ways:


Function Declaration:

function greet(name) {

return "Hello " + name;

}


Function Expression:

const greet = function(name) {

return "Hello " + name;

};


Named Function Expression:

const greet = function greetUser(name) {

return "Hello " + name;

};


2. this Binding

Normal functions have their own this context.

The value of this depends on how the function is called.


Example (Object Method):

const person = {

name: "Shamim",

greet: function() {

console.log("Hello " + this.name);

}

};

person.greet(); // "Hello Shamim"


Example (Lost Context):

const person = {

name: "Shamim",

greet: function() {

console.log("Hello " + this.name);

}

};

const greetFunc = person.greet;

greetFunc(); // "Hello undefined" (this refers to global object)


3. arguments Object

Normal functions have access to the arguments object.

This is an array-like object containing all arguments passed to the function.


function sum() {

let total = 0;

for (let i = 0; i < arguments.length; i++) {

total += arguments[i];

}

return total;

}

sum(1, 2, 3, 4); // 10


4. Can Be Used as Constructor

Normal functions can be used with the new keyword to create objects.


function Person(name, age) {

this.name = name;

this.age = age;

}

const shamim = new Person("Shamim", 25);


5. Hoisting

Function declarations are fully hoisted.

You can call them before they are defined in the code.


greet("Shamim"); // Works fine

function greet(name) {

return "Hello " + name;

}


Function expressions are not hoisted:

greet("Shamim"); // ReferenceError

const greet = function(name) {

return "Hello " + name;

};


ARROW FUNCTIONS


1. Syntax

Arrow functions provide a shorter syntax using the arrow notation.

They were introduced in ES6.


Basic Syntax:

const greet = (name) => {

return "Hello " + name;

};


Implicit Return (single expression):(means no need to write return keyword)

const greet = (name) => "Hello " + name;


Single Parameter (parentheses optional):

const greet = name => "Hello " + name;


No Parameters:

const greet = () => "Hello World";


Multiple Parameters:

const add = (a, b) => a + b;


2. this Binding (Lexical this)

Arrow functions do NOT have their own this context.

They inherit this from the surrounding scope (lexical scoping).


Example (Object Method):

const person = {

name: "Shamim",

greet: () => {

console.log("Hello " + this.name);

}

};

person.greet(); // "Hello undefined" (this refers to outer scope, not person)


Example (Callback in Object Method):

const person = {

name: "Shamim",

hobbies: ["coding", "reading"],

printHobbies: function() {

this.hobbies.forEach((hobby) => {

console.log(this.name + " likes " + hobby);

});

}

};

person.printHobbies();

// "Shamim likes coding"

// "Shamim likes reading"


With normal function, this would fail:

const person = {

name: "Shamim",

hobbies: ["coding", "reading"],

printHobbies: function() {

this.hobbies.forEach(function(hobby) {

console.log(this.name + " likes " + hobby);

});

}

};

person.printHobbies();

// "undefined likes coding"

// "undefined likes reading"


3. No arguments Object

Arrow functions do NOT have their own arguments object.

Use rest parameters instead.


const sum = (...args) => {

return args.reduce((total, num) => total + num, 0);

};

sum(1, 2, 3, 4); // 10


4. Cannot Be Used as Constructor

Arrow functions cannot be used with the new keyword.

They will throw a TypeError.


const Person = (name, age) => {

this.name = name;

this.age = age;

};

const shamim = new Person("Shamim", 25); // TypeError: Person is not a constructor


5. No Hoisting

Arrow functions are not hoisted.

They must be defined before they are used.


greet("Shamim"); // ReferenceError

const greet = (name) => "Hello " + name;


6. Cannot Be Used as Methods

Arrow functions should not be used as object methods when you need this.


Bad Practice:

const person = {

name: "Shamim",

greet: () => {

console.log("Hello " + this.name); // this is not person

}

};


Good Practice:

const person = {

name: "Shamim",

greet() {

console.log("Hello " + this.name); // this is person

}

};


7. Implicit Return

Arrow functions can have implicit return for single expressions.

No need for return keyword or curly braces.


const square = (x) => x * x;

const getUser = () => ({ name: "Shamim", age: 25 });


Note: When returning an object literal, wrap it in parentheses.


WHEN TO USE NORMAL FUNCTIONS


Use normal functions when:

- You need dynamic this binding

- You need to use the arguments object

- You need to use the function as a constructor

- You are defining object methods

- You need function hoisting


WHEN TO USE ARROW FUNCTIONS


Use arrow functions when:

- You want to preserve the outer this context

- You are writing short, simple functions

- You are using callbacks (map, filter, forEach, etc.)

- You want cleaner syntax

- You do not need arguments object or constructor functionality


COMPARISON TABLE


Feature | Normal Function | Arrow Function

---------------------------|---------------------------|---------------------------

Syntax | function name() {} | () => {}

this Binding | Dynamic (call-site) | Lexical (outer scope)

arguments Object | Available | Not Available

Constructor | Can be used with new | Cannot be used with new

Hoisting | Yes (declarations) | No

Implicit Return | No | Yes (single expression)

Method Definition | Recommended | Not Recommended

Use in Callbacks | Works but verbose | Preferred

prototype Property | Yes | No

Best Use Case | Methods, Constructors | Callbacks, Short functions


###what is callback function in javascript###


A callback function is a function that is passed as an argument to another function and is executed after some operation has been completed.

example:

function sum(a, b, callback) {

const result = a + b;

callback(result);

}

sum(5, 3, (result) => {

console.log("The result is: " + result);

});

// Example using a callback function defined separately and passed as a parameter

const numbers = [1, 2, 3, 4, 5];

// Callback function to double a number

const doubleNumber = (num) => {

  return num * 2;

};

// Using the map method with the callback function

const doubledNumbers = numbers.map(doubleNumber);

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]

###Difference between map, reduce, and filter###


MAP

The map() method creates a new array by applying a given function to each element of the original array.

SYNTAX:

const newArray = arr.map((element, index, array) => {

// Return the new value for the element

});

example:

const numbers = [1, 2, 3, 4, 5];

const doubledNumbers = numbers.map((num) => num * 2);

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]

FILTER

The filter() method creates a new array with all elements that pass the test implemented by the provided function.

SYNTAX:

const filteredArray = arr.filter((element, index, array) => {

// Return true to keep the element, false to exclude it

});

example:

const numbers = [1, 2, 3, 4, 5];

const evenNumbers = numbers.filter((num) => num % 2 === 0);

console.log(evenNumbers); // Output: [2, 4]

REDUCE

The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.

SYNTAX:

const reducedValue = arr.reduce((accumulator, element, index, array) => {

// Return the new value for the accumulator

}, initialValue);

example:

const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce((acc, num) => acc + num, 0);

console.log(sum); // Output: 15




=====REACTJS=====

###Difference between props and state###

Props:

Props are read-only.

Props are immutable.

Props are used to pass data from parent to child components.

Props are used to configure a component.

Props are accessible via this.props in class components and props in functional components.


State:

State is mutable.

State is used to store data that can change over time.

State is used to store UI state.

State is accessible via this.state in class components and useState hook in functional components.

###Difference between state and local variable###


STATE IN REACT


1. Definition

State is a built-in React object used to store data that affects component rendering.

When state changes, the component re-renders automatically.


2. Mutability

State should be treated as immutable.

You cannot directly modify state; you must use setState or setter functions.


Class Component:

this.state = { count: 0 };

this.setState({ count: 1 }); // Correct

this.state.count = 1; // Wrong - Direct mutation


Functional Component:

const [count, setCount] = useState(0);

setCount(1); // Correct

count = 1; // Wrong - Direct mutation


3. Persistence

State persists across component re-renders.

The value is maintained throughout the component lifecycle.


Example:

function Counter() {

const [count, setCount] = useState(0);


const increment = () => {

setCount(count + 1); // State persists after re-render

};


return <button onClick={increment}>{count}</button>;

}


4. Triggers Re-render

Changing state triggers a component re-render.

React updates the UI automatically when state changes.


Example:

const [name, setName] = useState("Shamim");

setName("Akhter"); // Component re-renders with new name


5. Scope

State is accessible throughout the component.

Can be passed to child components as props.


Example:

function Parent() {

const [data, setData] = useState("Hello");

return <Child message={data} />;

}


6. Use Cases

- Storing form input values

- Tracking UI state (open/closed, visible/hidden)

- Managing data fetched from APIs

- Controlling conditional rendering

- Tracking user interactions


LOCAL VARIABLES


1. Definition

Local variables are regular JavaScript variables declared inside a function or block.

They follow standard JavaScript scoping rules.


2. Mutability

Local variables are mutable.

You can directly change their values.


Example:

function example() {

let count = 0;

count = 1; // Allowed - Direct mutation

count++; // Allowed

}


3. Persistence

Local variables do NOT persist across re-renders.

They are reset every time the function runs.


Example:

function Counter() {

let count = 0; // Reset to 0 on every render


const increment = () => {

count++; // This won't work as expected

console.log(count); // Shows updated value

};


return <button onClick={increment}>{count}</button>; // Always shows 0

}


4. Does NOT Trigger Re-render

Changing a local variable does NOT trigger a re-render.

The UI will not update when the variable changes.


Example:

function Example() {

let message = "Hello";


const changeMessage = () => {

message = "World"; // Variable changes but UI doesn't update

};


return <div>{message}</div>; // Always shows "Hello"

}


5. Scope

Local variables are accessible only within the function or block where they are defined.

They cannot be passed to child components (they reset on re-render).


Example:

function example() {

let x = 10; // Only accessible in this function

if (true) {

let y = 20; // Only accessible in this block

}

}


6. Use Cases

- Temporary calculations

- Loop counters

- Intermediate values in computations

- Data that doesn't affect rendering

- Helper variables for logic


KEY DIFFERENCES SUMMARY


State:

- Managed by React

- Triggers re-render when changed

- Persists across re-renders

- Should be treated as immutable

- Used for data that affects UI

- Accessible throughout component


Local Variables:

- Regular JavaScript variables

- Does NOT trigger re-render

- Reset on every re-render

- Can be directly mutated

- Used for temporary data

- Limited to function/block scope


COMPARISON TABLE


Feature | State | Local Variable

---------------------------|---------------------------|---------------------------

Managed By | React | JavaScript

Triggers Re-render | Yes | No

Persists Across Re-renders | Yes | No

Mutability | Immutable (use setter) | Mutable (direct change)

Scope | Component-wide | Function/Block scope

Use Case | UI-affecting data | Temporary calculations

Declaration | useState / this.state | let / const / var

Updates UI | Yes | No

Can Pass to Children | Yes (as props) | No (resets on re-render)



###React hooks usage###


WHAT ARE REACT HOOKS


Hooks are functions that let you use React features in functional components.

They were introduced in React 16.8 to allow state and lifecycle features without writing class components.

Hooks always start with the word "use" (useState, useEffect, useContext, etc.).


RULES OF HOOKS


1. Only Call Hooks at the Top Level

Do not call hooks inside loops, conditions, or nested functions.

Always use hooks at the top level of your React function.


Wrong:

if (condition) {

const [count, setCount] = useState(0); // Error

}


Correct:

const [count, setCount] = useState(0);

if (condition) {

// Use count here

}


2. Only Call Hooks from React Functions

Call hooks from React functional components or custom hooks.

Do not call hooks from regular JavaScript functions.


Wrong:

function regularFunction() {

const [count, setCount] = useState(0); // Error

}


Correct:

function MyComponent() {

const [count, setCount] = useState(0); // Correct

}


COMMONLY USED REACT HOOKS


1. useState


Purpose:

Adds state to functional components.

Returns an array with current state value and a function to update it.


Syntax:

const [state, setState] = useState(initialValue);


Example:

function Counter() {

const [count, setCount] = useState(0);


return (

<div>

<p>Count: {count}</p>

<button onClick={() => setCount(count + 1)}>Increment</button>

<button onClick={() => setCount(count - 1)}>Decrement</button>

<button onClick={() => setCount(0)}>Reset</button>

</div>

);

}


Multiple State Variables:

function Form() {

const [name, setName] = useState("");

const [email, setEmail] = useState("");

const [age, setAge] = useState(0);


return (

<form>

<input value={name} onChange={(e) => setName(e.target.value)} />

<input value={email} onChange={(e) => setEmail(e.target.value)} />

<input value={age} onChange={(e) => setAge(e.target.value)} />

</form>

);

}


State with Objects:

function User() {

const [user, setUser] = useState({ name: "", age: 0 });


const updateName = (name) => {

setUser({ ...user, name }); // Spread to keep other properties

};


return <div>{user.name}</div>;

}


State with Arrays:

function TodoList() {

const [todos, setTodos] = useState([]);


const addTodo = (todo) => {

setTodos([...todos, todo]); // Add new item

};


const removeTodo = (index) => {

setTodos(todos.filter((_, i) => i !== index)); // Remove item

};


return <div>{todos.map((todo, i) => <p key={i}>{todo}</p>)}</div>;

}


2. useEffect


Purpose:

Performs side effects in functional components.

Replaces componentDidMount, componentDidUpdate, and componentWillUnmount.


Syntax:

useEffect(() => {

// Side effect code

return () => {

// Cleanup code (optional)

};

}, [dependencies]);


Example (Run on Every Render):

function Example() {

const [count, setCount] = useState(0);


useEffect(() => {

console.log("Component rendered");

}); // No dependency array - runs on every render


return <button onClick={() => setCount(count + 1)}>{count}</button>;

}


Example (Run Once on Mount):

function Example() {

useEffect(() => {

console.log("Component mounted");

}, []); // Empty array - runs only once


return <div>Hello</div>;

}


Example (Run When Dependency Changes):

function Example() {

const [count, setCount] = useState(0);


useEffect(() => {

console.log("Count changed:", count);

}, [count]); // Runs when count changes


return <button onClick={() => setCount(count + 1)}>{count}</button>;

}


Example (Cleanup Function):

function Timer() {

const [seconds, setSeconds] = useState(0);


useEffect(() => {

const interval = setInterval(() => {

setSeconds(s => s + 1);

}, 1000);


return () => {

clearInterval(interval); // Cleanup on unmount

};

}, []);


return <div>Seconds: {seconds}</div>;

}


Example (Fetching Data):

function UserProfile({ userId }) {

const [user, setUser] = useState(null);

const [loading, setLoading] = useState(true);


useEffect(() => {

setLoading(true);

fetch(`/api/users/${userId}`)

.then(response => response.json())

.then(data => {

setUser(data);

setLoading(false);

});

}, [userId]); // Re-fetch when userId changes


if (loading) return <div>Loading...</div>;

return <div>{user.name}</div>;

}


3. useContext


Purpose:

Accesses context values without using Context.Consumer.

Simplifies consuming context in functional components.


Syntax:

const value = useContext(MyContext);


Example:

const ThemeContext = React.createContext("light");


function App() {

return (

<ThemeContext.Provider value="dark">

<Toolbar />

</ThemeContext.Provider>

);

}


function Toolbar() {

return <ThemedButton />;

}


function ThemedButton() {

const theme = useContext(ThemeContext);

return <button className={theme}>I am {theme}</button>;

}


4. useRef


Purpose:

Creates a mutable reference that persists across re-renders.

Does not trigger re-render when the value changes.

Commonly used to access DOM elements.


Syntax:

const refContainer = useRef(initialValue);


Example (Accessing DOM Elements):

function TextInput() {

const inputRef = useRef(null);


const focusInput = () => {

inputRef.current.focus();

};


return (

<div>

<input ref={inputRef} type="text" />

<button onClick={focusInput}>Focus Input</button>

</div>

);

}


Example (Storing Previous Value):

function Counter() {

const [count, setCount] = useState(0);

const prevCountRef = useRef();


useEffect(() => {

prevCountRef.current = count;

});


const prevCount = prevCountRef.current;


return (

<div>

<p>Current: {count}, Previous: {prevCount}</p>

<button onClick={() => setCount(count + 1)}>Increment</button>

</div>

);

}


Example (Storing Mutable Value):

function Timer() {

const [seconds, setSeconds] = useState(0);

const intervalRef = useRef(null);


const start = () => {

intervalRef.current = setInterval(() => {

setSeconds(s => s + 1);

}, 1000);

};


const stop = () => {

clearInterval(intervalRef.current);

};


return (

<div>

<p>Seconds: {seconds}</p>

<button onClick={start}>Start</button>

<button onClick={stop}>Stop</button>

</div>

);

}


5. useReducer


Purpose:

Alternative to useState for complex state logic.

Similar to Redux reducer pattern.

Useful when state depends on previous state or has multiple sub-values.


Syntax:

const [state, dispatch] = useReducer(reducer, initialState);


Example:

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;

}

}


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>

<button onClick={() => dispatch({ type: "reset" })}>Reset</button>

</div>

);

}


6. useMemo


Purpose:

Memoizes expensive calculations.

Only re-computes when dependencies change.

Optimizes performance by avoiding unnecessary calculations.


Syntax:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);


Example:

function ExpensiveComponent({ numbers }) {

const sum = useMemo(() => {

console.log("Calculating sum...");

return numbers.reduce((total, num) => total + num, 0);

}, [numbers]); // Only recalculate when numbers change


return <div>Sum: {sum}</div>;

}


7. useCallback


Purpose:

Memoizes callback functions.

Returns the same function reference unless dependencies change.

Prevents unnecessary re-renders of child components.


Syntax:

const memoizedCallback = useCallback(() => {

doSomething(a, b);

}, [a, b]);


Example:

function Parent() {

const [count, setCount] = useState(0);

const [other, setOther] = useState(0);


const increment = useCallback(() => {

setCount(c => c + 1);

}, []); // Function reference stays the same


return (

<div>

<p>Count: {count}</p>

<p>Other: {other}</p>

<Child onIncrement={increment} />

<button onClick={() => setOther(other + 1)}>Change Other</button>

</div>

);

}


function Child({ onIncrement }) {

console.log("Child rendered");

return <button onClick={onIncrement}>Increment</button>;

}


CUSTOM HOOKS


Custom hooks are reusable functions that use built-in hooks.

They allow you to extract component logic into reusable functions.

Custom hooks must start with "use" prefix.


Example (useLocalStorage):

function useLocalStorage(key, initialValue) {

const [value, setValue] = useState(() => {

const stored = localStorage.getItem(key);

return stored ? JSON.parse(stored) : initialValue;

});


useEffect(() => {

localStorage.setItem(key, JSON.stringify(value));

}, [key, value]);


return [value, setValue];

}


Usage:

function App() {

const [name, setName] = useLocalStorage("name", "");


return (

<input

value={name}

onChange={(e) => setName(e.target.value)}

/>

);

}


Example (useFetch):

function useFetch(url) {

const [data, setData] = useState(null);

const [loading, setLoading] = useState(true);

const [error, setError] = useState(null);


useEffect(() => {

setLoading(true);

fetch(url)

.then(response => response.json())

.then(data => {

setData(data);

setLoading(false);

})

.catch(error => {

setError(error);

setLoading(false);

});

}, [url]);


return { data, loading, error };

}


Usage:

function UserList() {

const { data, loading, error } = useFetch("/api/users");


if (loading) return <div>Loading...</div>;

if (error) return <div>Error: {error.message}</div>;

return <div>{data.map(user => <p key={user.id}>{user.name}</p>)}</div>;

}


Example (useToggle):

function useToggle(initialValue = false) {

const [value, setValue] = useState(initialValue);


const toggle = useCallback(() => {

setValue(v => !v);

}, []);


return [value, toggle];

}


Usage:

function Modal() {

const [isOpen, toggleOpen] = useToggle(false);


return (

<div>

<button onClick={toggleOpen}>Toggle Modal</button>

{isOpen && <div>Modal Content</div>}

</div>

);

}


HOOKS COMPARISON TABLE


Hook | Purpose | Returns | Triggers Re-render

--------------|-----------------------------------|----------------------|-------------------

useState | Manage component state | [state, setState] | Yes

useEffect | Handle side effects | Cleanup function | No

useContext | Access context values | Context value | Yes (when context changes)

useRef | Store mutable reference | Ref object | No

useReducer | Manage complex state | [state, dispatch] | Yes

useMemo | Memoize expensive calculations | Memoized value | No

useCallback | Memoize callback functions | Memoized function | No


WHEN TO USE EACH HOOK


useState:

- Simple state management

- Independent state values

- Form inputs, toggles, counters


useEffect:

- Data fetching

- Subscriptions

- DOM manipulation

- Timers and intervals

- Cleanup operations


useContext:

- Accessing global state

- Theme, authentication, language

- Avoiding prop drilling


useRef:

- Accessing DOM elements

- Storing mutable values that don't trigger re-render

- Keeping previous values

- Storing timer IDs


useReducer:

- Complex state logic

- Multiple sub-values

- State transitions depend on previous state

- Alternative to Redux for local state


useMemo:

- Expensive calculations

- Filtering or sorting large arrays

- Complex computations

- Preventing unnecessary recalculations


useCallback:

- Passing callbacks to optimized child components

- Preventing unnecessary re-renders

- Dependencies for other hooks

- Event handlers passed as props


COMMON MISTAKES TO AVOID


1. Calling Hooks Conditionally

Wrong:

if (condition) {

useState(0); // Error

}


Correct:

const [state, setState] = useState(0);

if (condition) {

setState(1);

}


2. Missing Dependencies in useEffect

Wrong:

useEffect(() => {

console.log(count);

}, []); // Missing count dependency


Correct:

useEffect(() => {

console.log(count);

}, [count]);


3. Not Cleaning Up in useEffect

Wrong:

useEffect(() => {

const interval = setInterval(() => {}, 1000);

}, []); // Memory leak


Correct:

useEffect(() => {

const interval = setInterval(() => {}, 1000);

return () => clearInterval(interval);

}, []);


4. Overusing useMemo and useCallback

These hooks have overhead. Only use them when:

- Calculations are truly expensive

- Preventing re-renders of child components

- Dependencies in other hooks


5. Mutating State Directly

Wrong:

const [user, setUser] = useState({ name: "Shamim" });

user.name = "Akhter"; // Direct mutation


Correct:

setUser({ ...user, name: "Akhter" });


BEST PRACTICES


- Keep hooks at the top level of your component

- Use descriptive names for custom hooks

- Extract reusable logic into custom hooks

- Keep useEffect focused on one concern

- Always include all dependencies in dependency arrays

- Use ESLint plugin for hooks to catch mistakes

- Prefer multiple useState calls over one complex state object

- Use useReducer for complex state logic

- Clean up side effects in useEffect return function


###Counter using useState and extending it with a custom hook to store previous value###


BASIC COUNTER WITH useState


Step 1: Simple Counter Component


function Counter() {

const [count, setCount] = useState(0);


const increment = () => {

setCount(count + 1);

};


const decrement = () => {

setCount(count - 1);

};


const reset = () => {

setCount(0);

};


return (

<div>

<h2>Counter: {count}</h2>

<button onClick={increment}>Increment</button>

<button onClick={decrement}>Decrement</button>

<button onClick={reset}>Reset</button>

</div>

);

}


Explanation:

- useState(0) initializes count to 0

- setCount updates the count value

- Each button click triggers the respective function

- Component re-renders when count changes


CREATING A CUSTOM HOOK TO STORE PREVIOUS VALUE


Step 2: usePrevious Custom Hook


function usePrevious(value) {

const ref = useRef();


useEffect(() => {

ref.current = value;

});


return ref.current;

}


How it works:

- useRef creates a mutable reference that persists across re-renders

- useEffect runs after every render and stores the current value

- On the next render, ref.current still holds the previous value

- The hook returns the previous value


Why this works:

1. Component renders with new value

2. usePrevious is called with new value

3. ref.current still contains old value (from previous render)

4. Hook returns old value

5. useEffect runs after render and updates ref.current to new value

6. Next render, the cycle repeats


EXTENDED COUNTER WITH PREVIOUS VALUE


Step 3: Counter with Previous Value Display


function Counter() {

const [count, setCount] = useState(0);

const prevCount = usePrevious(count);


const increment = () => {

setCount(count + 1);

};


const decrement = () => {

setCount(count - 1);

};


const reset = () => {

setCount(0);

};


return (

<div>

<h2>Current Count: {count}</h2>

<h3>Previous Count: {prevCount !== undefined ? prevCount : "N/A"}</h3>

<button onClick={increment}>Increment</button>

<button onClick={decrement}>Decrement</button>

<button onClick={reset}>Reset</button>

</div>

);

}


Explanation:

- usePrevious(count) returns the previous value of count

- On first render, prevCount is undefined

- After first update, prevCount shows the previous value

- Display "N/A" when there is no previous value


COMPLETE IMPLEMENTATION


Step 4: Full Code with Both Hooks


import React, { useState, useEffect, useRef } from 'react';


Custom Hook:

function usePrevious(value) {

const ref = useRef();


useEffect(() => {

ref.current = value;

});


return ref.current;

}


Counter Component:

function Counter() {

const [count, setCount] = useState(0);

const prevCount = usePrevious(count);


const increment = () => {

setCount(count + 1);

};


const decrement = () => {

setCount(count - 1);

};


const reset = () => {

setCount(0);

};


const incrementByFive = () => {

setCount(count + 5);

};


return (

<div style={{ padding: '20px', textAlign: 'center' }}>

<h1>Counter with Previous Value</h1>

<div style={{ fontSize: '24px', margin: '20px' }}>

<p>Current Count: <strong>{count}</strong></p>

<p>Previous Count: <strong>{prevCount !== undefined ? prevCount : "N/A"}</strong></p>

{prevCount !== undefined && (

<p>Change: <strong>{count - prevCount}</strong></p>

)}

</div>

<div>

<button onClick={increment}>+1</button>

<button onClick={decrement}>-1</button>

<button onClick={incrementByFive}>+5</button>

<button onClick={reset}>Reset</button>

</div>

</div>

);

}


export default Counter;


ENHANCED VERSION WITH MORE FEATURES


Step 5: Advanced Counter with History


function usePrevious(value) {

const ref = useRef();


useEffect(() => {

ref.current = value;

});


return ref.current;

}


function Counter() {

const [count, setCount] = useState(0);

const [step, setStep] = useState(1);

const prevCount = usePrevious(count);

const prevStep = usePrevious(step);


const increment = () => {

setCount(count + step);

};


const decrement = () => {

setCount(count - step);

};


const reset = () => {

setCount(0);

};


const handleStepChange = (e) => {

setStep(Number(e.target.value));

};


return (

<div>

<h1>Advanced Counter</h1>


<div>

<h2>Current Count: {count}</h2>

<h3>Previous Count: {prevCount !== undefined ? prevCount : "N/A"}</h3>

{prevCount !== undefined && (

<p>Difference: {count - prevCount}</p>

)}

</div>


<div>

<label>

Step Value:

<input

type="number"

value={step}

onChange={handleStepChange}

min="1"

/>

</label>

{prevStep !== undefined && prevStep !== step && (

<p>Previous Step: {prevStep}</p>

)}

</div>


<div>

<button onClick={increment}>Increment by {step}</button>

<button onClick={decrement}>Decrement by {step}</button>

<button onClick={reset}>Reset</button>

</div>

</div>

);

}


ALTERNATIVE: USING useReducer WITH PREVIOUS VALUE


Step 6: Counter with useReducer


function usePrevious(value) {

const ref = useRef();


useEffect(() => {

ref.current = value;

});


return ref.current;

}


function counterReducer(state, action) {

switch (action.type) {

case 'increment':

return { count: state.count + 1 };

case 'decrement':

return { count: state.count - 1 };

case 'reset':

return { count: 0 };

case 'incrementBy':

return { count: state.count + action.payload };

default:

return state;

}

}


function Counter() {

const [state, dispatch] = useReducer(counterReducer, { count: 0 });

const prevCount = usePrevious(state.count);


return (

<div>

<h2>Current: {state.count}</h2>

<h3>Previous: {prevCount !== undefined ? prevCount : "N/A"}</h3>

<button onClick={() => dispatch({ type: 'increment' })}>+1</button>

<button onClick={() => dispatch({ type: 'decrement' })}>-1</button>

<button onClick={() => dispatch({ type: 'incrementBy', payload: 5 })}>+5</button>

<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>

</div>

);

}


KEY CONCEPTS EXPLAINED


1. Why useRef for Previous Value:

- useRef does not trigger re-render when updated

- Value persists across re-renders

- Perfect for storing previous state without causing infinite loops


2. Why useEffect Without Dependencies:

- Runs after every render

- Updates ref.current with the latest value

- Ensures previous value is always one render behind


3. Timing of Updates:

Render 1: count = 0, prevCount = undefined

User clicks increment

Render 2: count = 1, prevCount = 0 (ref updated after render 1)

User clicks increment

Render 3: count = 2, prevCount = 1 (ref updated after render 2)


4. Common Use Cases:

- Tracking value changes

- Showing difference between current and previous

- Conditional rendering based on value change

- Animation triggers

- Undo functionality


COMPARISON: useState vs useRef


Feature | useState | useRef

---------------------------|-----------------------|------------------------

Triggers Re-render | Yes | No

Persists Across Renders | Yes | Yes

Mutable | No (use setter) | Yes (direct mutation)

Use for UI Data | Yes | No

Use for Previous Values | No | Yes

Initialization | useState(value) | useRef(value)

Access Value | state | ref.current

Update Value | setState(newValue) | ref.current = newValue


BEST PRACTICES


1. Always check if previous value is undefined on first render

2. Use usePrevious for tracking state changes

3. Combine with useState for reactive UI updates

4. Use useRef for non-reactive data storage

5. Keep custom hooks reusable and generic

6. Add proper TypeScript types for production code

7. Consider using useReducer for complex state logic

8. Document custom hooks with clear examples