import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return setCount(count + 1)}>{count};
}?
import React, { useEffect } from 'react';
function Example() {
useEffect(() => {
document.title = 'Hello, world!';
}, []); // Empty array means it runs once on mount
return <div>Check the title!</div>;
}
const value = useContext(MyContext);?
useReducer (for complex state logic), useCallback (memoizes functions), and useMemo (memoizes values), plus you can create custom Hooks to reuse logic across components.
Hooks were introduced in React 16.8 to solve several issues with class components and to make state management and side effects easier in functional components. Here are the main reasons:
this binding, lifecycle methods, and boilerplate code.componentDidMount, componentDidUpdate, componentWillUnmount), which often result in duplicated or scattered logic.useEffect allow handling side effects in a more organized way.useMemo and useCallback help optimize performance by reducing unnecessary re-renders.Hooks in React follow a few fundamental rules to ensure they work correctly. These rules are enforced by React's linter plugin (eslint-plugin-react-hooks).
if (someCondition) {
const [state, setState] = useState(0); // Hook inside condition
}
const [state, setState] = useState(0); // Always at the top level
if (someCondition) {
console.log(state);
}
function someUtilityFunction() {
const [count, setCount] = useState(0); // Hooks inside a regular function
}
function MyComponent() {
const [count, setCount] = useState(0); // Hooks inside a functional component
}
use Prefix for Custom Hooksuse prefix (e.g., useMyHook).function useCustomHook() {
const [value, setValue] = useState(0);
return [value, setValue];
}
function customHook() { // No "use" prefix
const [value, setValue] = useState(0);
return [value, setValue];
}
React provides an ESLint plugin:
npm install eslint-plugin-react-hooks --save-dev
This will warn you if you break any Hook rules.
useCallback Hooks is used to memoize functions, and prevent unnecessary re-rendering of child components that rely on those components. The useCallback function in React is mainly used to keep a reference to a function constant across multiple re-renders. This feature becomes useful when we want to prevent the unnecessary re-creation of functions, especially when we need to pass them as dependencies to other hooks such as useMemo or useEffect. | useMemo | useCallback |
|---|---|
| Memoizes a value (result of a function) | Memoizes a function |
| Memoized value | Memoized function reference |
| When we need to memoize a calculated value | When we need to memoize a function |
| Recalculates when any dependency changes | Recreates the function only when any dependency changes |
| Example: Memoizing the result of expensive computations | Example: Memoizing event handler functions to prevent re-renders |
| useState | useReducer |
|---|---|
| Handles state with a single value | Handles state with more complex logic and multiple values |
| Simple to use, suitable for basic state needs | More complex, suitable for managing complex state logic |
| Simple state updates, like toggles or counters | Managing state with complex transitions and logic |
| Directly updates state with a new value | Updates state based on dispatched actions and logic |
| Not used | Requires a reducer function to determine state changes |
| Logic is dispersed where state is used | Logic is centralized within the reducer function |
useRef is used in React functional components when we need to keep a mutable value around across renders without triggering a re-render. It's commonly used for accessing DOM elements, caching values, or storing mutable variables. we can use useRef to manage focus within wer components, such as focusing on a specific input element when a condition is met without triggering re-renders. useLawetEffect is similar to useEffect but fires synchronously after all DOM mutations. It's useful for reading from the DOM or performing animations before the browser paints. Due to its synchronous nature, excessive usage of useLawetEffect may potentially impact performance, especially if the logic within it is computationally intensive or blocking. It's essential to use useLawetEffect judiciously and consider performance implications carefully. import { useEffect } from 'react';
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}
// Usage:
function MyComponent() {
useDocumentTitle('Hello FTL!');
return <div>FreeTimeLearning content...</div>;
}? According to React's documentation and best practices, you cannot directly use React Hooks inside class components. Here's a breakdown:
Hooks and Function Components :
Why Not in Class Components?
Mixing Components :
Workarounds :
In essence, React Hooks are specifically tied to function components, and attempting to use them within class components will lead to errors.
React provides several built-in Hooks to manage state, lifecycle, and other side effects in functional components. Here are some of the most commonly used ones:
useStateimport { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
useEffectimport { useState, useEffect } from "react";
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(interval); // Cleanup on unmount
}, []); // Empty dependency array → runs once
return <p>Timer: {seconds}s</p>;
}
useMemoimport { useMemo, useState } from "react";
function ExpensiveCalculation({ num }) {
const squaredNumber = useMemo(() => {
console.log("Calculating...");
return num * num;
}, [num]);
return <p>Squared: {squaredNumber}</p>;
}
useCallbackimport { useCallback, useState } from "react";
function ButtonComponent({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<ButtonComponent onClick={handleClick} />
</div>
);
}
useRefimport { useRef, useEffect } from "react";
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
useImperativeHandleReact.forwardRef.import { useImperativeHandle, useRef, forwardRef } from "react";
const CustomInput = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
const inputRef = useRef();
return <input ref={inputRef} />;
});
function Parent() {
const inputRef = useRef();
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
</div>
);
}
useContext<Consumer>.import { createContext, useContext } from "react";
const ThemeContext = createContext("light");
function ThemeComponent() {
const theme = useContext(ThemeContext);
return <p>Current theme: {theme}</p>;
}
useReduceruseState, useful for complex state logic.import { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "increment": return { count: state.count + 1 };
case "decrement": return { count: state.count - 1 };
default: throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</div>
);
}
useLayoutEffectuseEffect, but runs synchronously after DOM mutations.import { useLayoutEffect, useRef } from "react";
function LayoutEffectExample() {
const divRef = useRef();
useLayoutEffect(() => {
divRef.current.style.color = "red";
});
return <div ref={divRef}>This text will be red immediately</div>;
}
useDebugValueimport { useDebugValue, useState } from "react";
function useCustomHook(value) {
useDebugValue(value > 5 ? "Large" : "Small");
return useState(value);
}
| Hook | Purpose |
|---|---|
useState |
Manages local state |
useEffect |
Handles side effects |
useMemo |
Memoizes expensive computations |
useCallback |
Memoizes functions |
useRef |
Creates mutable references |
useImperativeHandle |
Customizes ref exposure |
useContext |
Accesses Context API values |
useReducer |
Manages complex state logic |
useLayoutEffect |
Runs synchronously after render |
useDebugValue |
Adds debug info for custom hooks |
useContext Hook in ReactThe useContext Hook is used to access values from React’s Context API without needing to wrap components in Consumer components. It helps manage global state efficiently by avoiding prop drilling.
useContext?useContext?First, create a context using createContext().
import { createContext } from "react";
const ThemeContext = createContext("light"); // Default value
ProviderWrap your components inside a Provider to supply the context value.
function App() {
return (
<ThemeContext.Provider value="dark">
<ChildComponent />
</ThemeContext.Provider>
);
}
useContextInstead of using <ThemeContext.Consumer>, we use useContext().
import { useContext } from "react";
function ChildComponent() {
const theme = useContext(ThemeContext); // Access context value
return <p>Current Theme: {theme}</p>;
}
import React, { useContext, createContext } from "react";
// 1 Create Context
const ThemeContext = createContext("light");
function ChildComponent() {
// 2 Use useContext to get the value
const theme = useContext(ThemeContext);
return <p>Current Theme: {theme}</p>;
}
function App() {
return (
// 2 Provide context value
<ThemeContext.Provider value="dark">
<ChildComponent />
</ThemeContext.Provider>
);
}
export default App;
* Output: Current Theme: dark
| Feature | useContext |
|---|---|
| Purpose | Access global state without prop drilling |
| Works with | React Context API |
| Alternative to | Prop drilling, Redux (for simple cases) |
| Best for | Theme, Auth, Global Settings |
| Not ideal for | Frequently changing state (use useState or useReducer instead) |
The useContext hook in React is a powerful tool that significantly alleviates the problem of "prop drilling." Here's how it works:
Understanding Prop Drilling :
How useContext Solves This :
React.createContext() to create a context object. This object holds the data you want to share.Context.Provider component.Provider component takes a value prop, which holds the data you want to make available to descendant components.Provider's tree can access the context value using the useContext hook.useContext hook takes the context object as an argument and returns the current context value.
In essence :
useContext allows you to establish a "global" data source within a portion of your component tree.
Key Benefits :
Therefore, useContext is a very useful tool for managing state that needs to be accessed by many deeply nested components.
Yes, you can use useContext without a Provider, but the result will be that you receive the default value that was defined when you created the context. Here's a breakdown:
React.createContext(defaultValue) :
React.createContext(), you can provide an optional defaultValue.defaultValue serves as a fallback value that useContext will return if no matching Provider is found in the component tree above the component calling useContext.useContext(MyContext) :
useContext(MyContext), React searches upwards in the component tree for the nearest MyContext.Provider.Provider is found, useContext returns the value prop of that Provider.Provider is found, useContext returns the defaultValue that was passed to React.createContext().
Therefore :
useContext without a surrounding Provider, you won't get an error. Instead, you'll simply get the default value.Providers for every test.In essence, the Provider's role is to override the default context value, and if it is not present, the default value is what is returned.
While useContext is excellent for simplifying prop drilling, improper use can lead to performance issues, especially in large applications. Here's how you can optimize performance when using useContext:
1. Minimize Context Value Changes :
useMemo to memoize the value. This prevents unnecessary re-renders when the derived value hasn't actually changed.
2. Scope Context Providers Appropriately :
Provider unless absolutely necessary. This can lead to unnecessary re-renders of all components that consume the context, even if they don't depend on the changed values.
3. Optimize Consumer Components :
React.memo to memoize components that consume the context. This prevents re-renders if the component's props and the context value haven't changed.
4. Consider Alternatives for Frequent Updates :
useReducer with Context:
useContext with useReducer. This allows you to centralize state logic and optimize updates.
Example of Memoization :
import React, { createContext, useContext, useState, useMemo, memo } from 'react';
const MyContext = createContext();
const MyProvider = ({ children }) => {
const [count, setCount] = useState(0);
const contextValue = useMemo(() => ({
count,
increment: () => setCount(count + 1),
}), [count]); // Memoize the context value
return (
<MyContext.Provider value={contextValue}>
{children}
</MyContext.Provider>
);
};
const MyConsumer = memo(() => { // Memoize the consumer
const { count, increment } = useContext(MyContext);
console.log("MyConsumer rendered");
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
});
export default function App() {
return (
<MyProvider>
<MyConsumer />
</MyProvider>
);
}
By following these optimization techniques, you can effectively use useContext without sacrificing performance in your React applications.
While useContext is a great tool for managing state, especially for avoiding prop drilling, it might not be the best fit for all situations. Here are some alternatives for state management in React, ranging from simpler to more complex solutions:
1. useReducer :
useReducer takes a reducer function and an initial state as arguments.2. Prop Drilling (For Simple Cases) :
3. External State Management Libraries :
4. Component Composition :
Choosing the Right Approach :
useState is sufficient.useReducer is a good choice.useContext is effective.Consider the complexity of your application, the size of your team, and your personal preferences when choosing a state management solution.
In React, useState is a Hook that allows you to add state to functional components. Essentially, it's a way to make functional components "remember" values between renders. Here's a breakdown :
What is State?
How useState Works:
Declaration:
useState from the react library.useState with an initial value.useState returns an array with two elements:
Using the State:
useState.Re-renders:
Key Concepts:
useState is the initial value of the state.useState is used to update the state.Example :
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
useState(0) initializes the count state to 0.setCount is the function used to update the count value.setCount(count + 1) increments the count and triggers a re-render.useState is a fundamental Hook in React that simplifies state management in functional components. When working with useState in React, updating the state correctly is crucial for ensuring your components behave as expected. Here's a breakdown of how to update state, along with important considerations:
1. Using the Setter Function:
useState returns an array containing the current state value and a setter function. This setter function is how you update the state.const [count, setCount] = useState(0);
setCount(count + 1); // Updates the count
2. Updating Based on the Previous State (Functional Updates) :
setCount((prevCount) => prevCount + 1);
In this example, prevCount represents the previous state value.
3. Updating Objects and Arrays :
useState doesn't automatically merge objects or arrays. Therefore, when updating these types of state, you need to create new copies of the data.const [user, setUser] = useState({ name: 'John', age: 30 });
setUser((prevUser) => ({
...prevUser, // Create a copy of the previous object
age: prevUser.age + 1, // Update the specific property
}));
const [items, setItems] = useState([1, 2, 3]);
setItems((prevItems) => [...prevItems, 4]); // Create a new array with the added item
Key Considerations :
By following these guidelines, you can ensure that your state updates are reliable and predictable.
When you update state in React using useState with the same value as the current state, React performs an optimization that prevents unnecessary re-renders. Here's a more detailed explanation:
React's Optimization :
Object.is comparison algorithm to determine if the new state value is the same as the current state value.Object.is determines that the values are identical, React will typically skip re-rendering the component and its children.Key Points :
Object.is comparison checks for reference equality. This means that if you create a new object or array with the same values as the previous one, React will consider them to be different, even if their contents are the same.While useState is excellent for simple state management, handling complex state can become challenging. Here's how you can effectively manage complex state with useState, along with considerations for when to use alternatives:
Techniques for Managing Complex State with useState:
const [formData, setFormData] = useState({
name: '',
email: '',
address: {
street: '',
city: '',
},
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
const handleAddressChange = (e) => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
address: {
...prevData.address,
[name]: value,
},
}));
};
useState Calls:
useState calls.useState with Custom Hooks:
useState and related functions.
When to Consider Alternatives :
useReducer is a better choice.useContext or an external state management library like Redux, Zustand, or Recoil.
Key Considerations :
useEffect is a React Hook that lets you perform side effects in functional components.
What are Side Effects?
Side effects are actions that affect things outside of the component's render output. Examples include:
setTimeout or setInterval.Why Use useEffect?
In functional components, you can't directly perform these side effects within the main body of the component. This is because:
useEffect provides a way to:
How useEffect Works:
useEffect takes two arguments:
Basic Usage :
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
// This effect runs after every render
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
Dependency Array:
[]) means the effect will only run once after the initial render.Cleanup Function:
useEffect(() => {
const timerID = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(timerID); // Cleanup function
};
}, []); // Runs only once
In essence :
useEffect allows you to synchronize your component with external systems or perform actions that are not directly related to rendering, while also providing a mechanism for cleaning up those actions.
useEffect is a versatile Hook in React that allows you to manage side effects in various ways. Here's a breakdown of the different ways you can use it:
1. Running Effects After Every Render (No Dependency Array):
This is the simplest form of useEffect.
The effect callback function will run after every render of the component.
Use this when you need to perform an action on every render, regardless of changes in props or state.
Example :
useEffect(() => {
console.log('Component rendered');
});
2. Running Effects Only Once (Empty Dependency Array):
By providing an empty dependency array ([]), you tell React to run the effect only once after the initial render.
This is useful for actions that should only happen once, such as fetching data on component mount or setting up an initial subscription.
Example :
useEffect(() => {
fetchData(); // Fetch data on component mount
}, []);
3. Running Effects Only When Dependencies Change (Dependency Array):
This is the most common and powerful way to use useEffect.
You provide a dependency array containing the values that the effect depends on (props or state).
The effect will only run when any of the values in the dependency array change.
This helps to optimize performance and prevent unnecessary re-renders.
Example :
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]); // Run effect only when 'count' changes
4. Cleaning Up Effects:
If your effect sets up a subscription, timer, or other resource that needs to be cleaned up, you can return a cleanup function from the effect callback.
The cleanup function will be called:
This helps to prevent memory leaks and other issues.
Example :
useEffect(() => {
const timerID = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(timerID); // Cleanup function
};
}, []);
5. Using Multiple useEffect Hooks:
You can use multiple useEffect hooks in a single component.
This allows you to separate different side effects and manage them independently.
This can improve code organization and readability.
Example :
useEffect(() => {
// Effect 1: Update document title
}, [count]);
useEffect(() => {
// Effect 2: Fetch data
}, []);
Key Considerations :
useEffect hooks to separate different side effects.By understanding these different ways to use useEffect, you can effectively manage side effects in your React components.
The dependency array in useEffect is a crucial part of controlling when and how your side effects run. Here's a detailed explanation of how it works:
Purpose of the Dependency Array :
useEffect Hook.
How React Uses the Dependency Array :
Initial Render :
Subsequent Renders :
Object.is comparison algorithm to check if any of the dependencies have changed.Effect Execution :
Different Scenarios :
[]) :
[dep1, dep2, ...]) :
useEffect, as it allows you to control when the effect runs based on specific dependencies.
Key Considerations :
useMemo or useCallback.In summary, the dependency array in useEffect allows you to precisely control when your side effects run, which is essential for optimizing performance and preventing bugs.
When you don't provide a dependency array to useEffect, the effect callback function will run after every render of the component. This has significant implications:
Here's what happens :
Execution After Every Render :
useEffect callback will be performed on each and every re-render.Potential Performance Issues :
Risk of Infinite Loops :
Unintended Side Effects :
In essence :
When to (Rarely) Use It :
Key Takeaway :
useEffect. This allows you to control when the effect runs and prevent unnecessary re-renders.Cleaning up side effects in useEffect is crucial for preventing memory leaks and ensuring your components behave correctly. Here's how you do it:
The Cleanup Function :
useEffect callback, you can return a function.
When the Cleanup Function Is Called :
useEffect Hook has a dependency array, and the values in that array change, React will:
Common Scenarios for Cleanup :
setTimeout or setInterval in your effect, you need to clear the timers in the cleanup function.
Example: Cleaning Up a Timer :
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const timerID = setInterval(() => {
setSeconds((prevSeconds) => prevSeconds + 1);
}, 1000);
return () => {
clearInterval(timerID); // Cleanup function
};
}, []);
return <div>Seconds: {seconds}</div>;
}
export default Timer;
In this example :
setInterval function sets up a timer.clearInterval(timerID), is returned from the useEffect callback.Key Points :
By using cleanup functions, you can keep your React applications clean and efficient.
useMemo is a React Hook that plays a crucial role in performance optimization by memoizing the result of a computation. Here's a breakdown of how it works and how it helps:
What is Memoization?
How useMemo Works :
Arguments :
useMemo takes two arguments:
Caching the Result :
useMemo executes the provided function and stores the result.useMemo checks the dependency array.useMemo returns the cached result from the previous render, without re-executing the computation function.useMemo re-executes the computation function, stores the new result, and returns it.
How useMemo Optimizes Performance :
Avoiding Expensive Computations :
useMemo can prevent it from being re-executed unnecessarily.Preventing Unnecessary Re-renders of Child Components :
useMemo to memoize the computed value, you can ensure that the child component only re-renders when the value changes.React.memo, which memoizes the entire child component.Reference Equality :
useMemo returns a memoized value. This is very useful when passing down values that rely on referential equality to prevent unneeded rerenders. For Example, if you are passing down an object, or an array as a prop. Without useMemo, a new object or array will be generated on every render, even if the contents are the same. This will cause the child component to rerender.Example :
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation({ a, b }) {
const result = useMemo(() => {
console.log('Calculating...');
// Simulate an expensive calculation
let sum = 0;
for (let i = 0; i < 100000000; i++) {
sum += a + b;
}
return sum;
}, [a, b]);
return <div>Result: {result}</div>;
}
function App() {
const [num1, setNum1] = useState(1);
const [num2, setNum2] = useState(2);
return (
<div>
<input type="number" value={num1} onChange={(e) => setNum1(parseInt(e.target.value))} />
<input type="number" value={num2} onChange={(e) => setNum2(parseInt(e.target.value))} />
<ExpensiveCalculation a={num1} b={num2} />
</div>
);
}
export default App;
In this example :
useMemo Hook memoizes the result of the expensive calculation.a or b changes.useMemo, the calculation would be re-executed on every render, even if a and b remained the same.By strategically using useMemo, you can significantly improve the performance of your React applications.
React Hooks don't have built-in support for asynchronous operations, but you can handle them using async functions, Promises, or libraries like axios and fetch. Here’s how you can manage async operations effectively.
useEffect for Side Effects (Fetching Data)Since useEffect runs after render, it’s a great place to fetch data from an API.
fetchimport { useState, useEffect } from "react";
function FetchData() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
if (!response.ok) throw new Error("Failed to fetch");
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []); // Empty dependency array → runs once
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <p>Title: {data?.title}</p>;
}
useEffect to run the request when the component mounts.async/await inside a function (not directly in useEffect).State updates using useState are synchronous in React, but you can use useEffect to react to state changes asynchronously.
import { useState, useEffect } from "react";
function AsyncCounter() {
const [count, setCount] = useState(0);
const [doubleCount, setDoubleCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setDoubleCount(count * 2);
}, 500);
return () => clearTimeout(timer); // Cleanup
}, [count]); // Runs when count changes
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<p>Count: {count}</p>
<p>Double (delayed): {doubleCount}</p>
</div>
);
}
useEffect?useCallback for Asynchronous FunctionsIf you're passing an async function as a prop to child components, wrap it in useCallback to prevent unnecessary re-renders.
import { useState, useCallback } from "react";
function FetchButton({ fetchData }) {
return <button onClick={fetchData}>Fetch Data</button>;
}
function ParentComponent() {
const [data, setData] = useState(null);
const fetchData = useCallback(async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/2");
const result = await response.json();
setData(result);
}, []);
return (
<div>
<FetchButton fetchData={fetchData} />
<p>Data: {data?.title}</p>
</div>
);
}
useCallback?fetchData.useReducer for Complex Async State LogicFor managing complex async state (e.g., loading, success, failure states), use useReducer.
import { useReducer, useEffect } from "react";
const initialState = {
loading: true,
data: null,
error: null,
};
function reducer(state, action) {
switch (action.type) {
case "SUCCESS":
return { loading: false, data: action.payload, error: null };
case "ERROR":
return { loading: false, data: null, error: action.payload };
default:
return state;
}
}
function FetchWithReducer() {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/3");
const result = await response.json();
dispatch({ type: "SUCCESS", payload: result });
} catch (err) {
dispatch({ type: "ERROR", payload: err.message });
}
}
fetchData();
}, []);
if (state.loading) return <p>Loading...</p>;
if (state.error) return <p>Error: {state.error}</p>;
return <p>Title: {state.data?.title}</p>;
}
useReducer?useContext for Asynchronous Global StateFor fetching data once and sharing it globally, use useContext with a provider.
import { createContext, useContext, useState, useEffect } from "react";
const DataContext = createContext();
function DataProvider({ children }) {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/4");
const result = await response.json();
setData(result);
}
fetchData();
}, []);
return <DataContext.Provider value={data}>{children}</DataContext.Provider>;
}
function DisplayData() {
const data = useContext(DataContext);
return <p>Title: {data?.title || "Loading..."}</p>;
}
function App() {
return (
<DataProvider>
<DisplayData />
</DataProvider>
);
}
useContext?| Best Practice | Do This |
|---|---|
Use useEffect for API calls |
Fetch data inside useEffect |
Use async inside functions, not useEffect directly |
Define async functions inside useEffect |
| Handle errors properly | Use try/catch and setError states |
| Cleanup side effects | Return a cleanup function inside useEffect |
Use useCallback for async functions in props |
Prevent unnecessary re-renders |
Use useReducer for complex state updates |
Manage loading, success, and error states |
React has strict rules for using Hooks, and one of the most important rules is:
Hooks must be called at the top level of a component and not inside loops, conditions, or nested functions.
This ensures that React preserves the order of Hooks across renders.
If you call Hooks inside a loop, condition, or nested function, React loses track of their order across renders, causing unpredictable behavior.
function Example({ condition }) {
if (condition) {
// WRONG: Hook inside a condition
const [count, setCount] = useState(0);
}
return <p>Count: {count}</p>; // count is not defined
}
Fix : Always declare Hooks at the top level:
function Example({ condition }) {
const [count, setCount] = useState(0); // Always at the top
return condition ? <p>Count: {count}</p> : <p>No count</p>;
}
Instead of conditionally using a Hook, conditionally render components that use Hooks.
function Counter() {
const [count, setCount] = useState(0);
return <p>Count: {count}</p>;
}
function Example({ condition }) {
return condition ? <Counter /> : <p>No counter</p>;
}
This way, Counter always initializes its Hooks properly.
If you need multiple instances of a Hook, store data in an array and map over it.
function WrongComponent() {
const items = [1, 2, 3];
items.forEach(item => {
// ? WRONG: Hooks inside a loop
const [count, setCount] = useState(0);
});
return <p>Invalid Hook usage!</p>;
}
function Counter({ id }) {
const [count, setCount] = useState(0);
return (
<p>
Item {id}: {count}
</p>
);
}
function CorrectComponent() {
const items = [1, 2, 3];
return (
<div>
{items.map(item => (
<Counter key={item} id={item} />
))}
</div>
);
}
* Each Counter component has its own useState, ensuring consistent behavior.
| Don't Do This | Do This Instead |
|---|---|
| Hooks inside loops | Use .map() to render components |
| Hooks inside conditions | Always declare Hooks at the top level |
| Hooks inside nested functions | Define the Hook outside the function |
React Hooks make development easier, but misusing them can lead to performance issues like unnecessary re-renders, stale closures, and memory leaks. Here are some common pitfalls and how to fix them.
useState Updates :If you call setState with the same value, React will still re-render the component.
function Counter() {
const [count, setCount] = useState(0);
console.log("Component re-rendered");
return <button onClick={() => setCount(0)}>Set to 0</button>;
}
* Issue: Clicking the button re-renders even though the state hasn’t changed.
* Fix : Use a function update to check the current state.
<button onClick={() => setCount(prev => (prev === 0 ? prev : 0))}>Set to 0</button>
useEffect :useEffect without proper dependenciesfunction App() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // Causes infinite loop
}, [count]);
return <p>Count: {count}</p>;
}
* Issue: Every time count updates, useEffect triggers another update.
* Fix: Use a function update or an appropriate dependency array.
useEffect(() => {
setCount(prev => prev + 1);
}, []); // Runs only once
useEffect :useEffectfunction App() {
const [count, setCount] = useState(0);
useEffect(() => {
const log = () => console.log(count); // Recreated every render
document.addEventListener("click", log);
return () => document.removeEventListener("click", log);
}, [count]); // Memory leak risk
}
* Issue: Every re-render creates a new log function, causing multiple event listeners.
* Fix: Use useCallback to memoize the function.
const log = useCallback(() => console.log(count), [count]);
useEffect(() => {
document.addEventListener("click", log);
return () => document.removeEventListener("click", log);
}, [log]);
useEffect :useEffectuseEffect(() => {
console.log(count); // May log an outdated count
}, []); // Missing [count]
* Issue : The effect captures the initial value of count, not the latest.
* Fix : Always add dependencies to the array.
useEffect(() => {
console.log(count);
}, [count]);
useMemo and useCallback :useMemo and useCallback when unnecessaryconst computedValue = useMemo(() => a * b, [a, b]); // Unnecessary if computation is simple
const handleClick = useCallback(() => console.log("Clicked"), []); // Doesn't need memoization
* Issue : These Hooks add complexity and should be used only for expensive computations.
* Fix : Use them only when necessary (e.g., for expensive calculations or preventing unnecessary prop changes).
function Parent() {
return <Child onClick={() => console.log("Clicked")} />; // New function every render
}
* Issue : Child re-renders every time even if onClick doesn’t change.
* Fix : Use useCallback to prevent unnecessary re-renders.
const handleClick = useCallback(() => console.log("Clicked"), []);
<Child onClick={handleClick} />;
useEffect(() => {
fetchData().then(data => setState(data)); // Possible memory leak
}, []);
* Issue : If the component unmounts before fetchData completes, React tries to update an unmounted component.
* Fix : Use a cleanup function.
useEffect(() => {
let isMounted = true;
fetchData().then(data => {
if (isMounted) setState(data);
});
return () => {
isMounted = false;
};
}, []);
| Bad Practice | Best Practice |
|---|---|
| Updating state unnecessarily | Only update if values change |
Missing dependencies in useEffect |
Always include necessary dependencies |
| Recreating functions inside components | Use useCallback for stable functions |
Using useMemo everywhere |
Use it only for expensive calculations |
Updating state inside useEffect without cleanup |
Use cleanup functions to avoid memory leaks |
When working with events like search inputs, scrolling, or resizing, it's important to optimize performance by reducing unnecessary function calls. Two common techniques for this are:
useEffect and setTimeoutDebouncing is useful when handling search inputs or resizing where you want to execute a function only after the user stops typing/moving.
useDebounce Hookimport { useState, useEffect } from "react";
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler); // Cleanup previous timer
}, [value, delay]);
return debouncedValue;
}
function SearchBar() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 500); // Waits 500ms after last input
useEffect(() => {
if (debouncedQuery) {
console.log("Fetching results for:", debouncedQuery);
}
}, [debouncedQuery]);
return (
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
);
}
useRef and setTimeout :Throttling ensures a function executes at most once per interval. It's useful for scrolling, resizing, or button spamming.
useThrottle Hookimport { useState, useEffect, useRef } from "react";
function useThrottle(value, delay) {
const [throttledValue, setThrottledValue] = useState(value);
const lastExecuted = useRef(Date.now());
useEffect(() => {
const handler = setTimeout(() => {
if (Date.now() - lastExecuted.current >= delay) {
setThrottledValue(value);
lastExecuted.current = Date.now();
}
}, delay - (Date.now() - lastExecuted.current));
return () => clearTimeout(handler);
}, [value, delay]);
return throttledValue;
}
function ScrollLogger() {
const [scrollPos, setScrollPos] = useState(0);
const throttledScrollPos = useThrottle(scrollPos, 1000); // Throttles updates every 1s
useEffect(() => {
const handleScroll = () => {
setScrollPos(window.scrollY);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return <p>Scroll Position: {throttledScrollPos}</p>;
}
| Feature | Debounce | Throttle |
|---|---|---|
| Use Case | Search input, API calls | Scroll, resize, click events |
| Execution | Fires after user stops | Fires at fixed intervals |
| Example | Auto-suggest search bar | Tracking scroll position |
When testing components that use React Hooks, the goal is to ensure they behave correctly across renders, state changes, and side effects. You can use Jest and React Testing Library (RTL) to write effective tests.
For components that use useState, useEffect, or other Hooks, test their behavior, not implementation details.
useState in a Counter Componentimport { render, screen, fireEvent } from "@testing-library/react";
import Counter from "./Counter"; // Component using useState
test("increments counter on button click", () => {
render(<Counter />);
const button = screen.getByText("Count: 0");
fireEvent.click(button);
expect(screen.getByText("Count: 1")).toBeInTheDocument();
});
render().fireEvent.click().useEffect Side Effects (API Calls)For components that fetch data inside useEffect, mock API calls using Jest.
useEffectimport { render, screen, waitFor } from "@testing-library/react";
import axios from "axios";
import Users from "./Users"; // Fetches users from API
jest.mock("axios");
test("fetches and displays users", async () => {
axios.get.mockResolvedValue({ data: [{ name: "Alice" }] });
render(<Users />);
expect(screen.getByText(/Loading/i)).toBeInTheDocument();
// Wait for async data to load
await waitFor(() => screen.getByText("Alice"));
expect(screen.getByText("Alice")).toBeInTheDocument();
});
jest.mock().waitFor() to wait for async updates.For testing standalone Hooks, use the renderHook utility from @testing-library/react-hooks.
useCounter Hookimport { renderHook, act } from "@testing-library/react";
import { useCounter } from "./useCounter";
test("increments counter", () => {
const { result } = renderHook(() => useCounter());
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
renderHook() to call the Hook.act() to simulate state updates.| Hook Used | Testing Approach |
|---|---|
useState |
Simulate user events, check UI updates |
useEffect |
Mock API calls, use waitFor() |
useContext |
Wrap component in context provider |
useReducer |
Dispatch actions, verify state changes |
useCustomHook |
Use renderHook(), trigger updates with act() |
| Points | Functional Components | Class Components |
|---|---|---|
| Declaration | These are nothing but JavaScript Functions. So, you declare it in the same manner as the JavaScript function. | On the other hand, class components are declared using the ES6 class. |
| Handling Props | Handling props is very straightforward. You can use any prop as an argument to a functional component that can be directly used inside HTML elements. | For class components, the props are handled differently. Here, we make use of the “this” keyword. |
| Handling State | Functional components use react hooks for handling state. | For the class components, we can't use the hooks, so for this case, for handling the state, we make use of a different syntax. |
| Constructor | For the functional components, constructors are not used. | For the class components, constructors are used for storing the state. |
| Render Method | In the functional component, there is no use of the render() method. | In the class component, it must have the render() method. |
| State | Props |
|---|---|
| States are managed within the component, similar to variables declared within a function. | Props get passed to the component similar to function parameters. |
| State is mutable(that is it can be modified). | Props are immutable(that is they cannot be modified). |
| You can read as well as modify states. | Props are read-only. |
class Count extends Component {
state = {
value: 0,
};
incrementValue = () => {
this.setState({
value: this.state.value + 1,
});
};
render() {
return (
<div>
<button onClick={this.incrementValue}>Count:{this.state.value}</button>
</div>
);
}
}
how can you rewrite this component using react hooks?
The equivalent code using function component and react hooks are shown below,
import React, { useState } from "react";
const Count = () => {
const [value, setvalue] = useState(0);
return (
<div>
<button
onClick={() => {
setCount(value + 1);
}}
>
Count: {value}
</button>
</div>
);
} import React, { useState } from "react";
const Count = () => {
const [value, setValue] = useState(0);
const increment= () => {
setCount((prevValue) => {
return prevValue + 1;
});
};
const decrement = () => {
setCount((prevValue) => {
return prevValue - 1;
});
};
return (
<div>
<strong>Count: {value}</strong>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}? import React,
{
useEffect,
useState
}
from 'react';
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("See the Effect here");
});
return (
<div>
<p>Count: {count}</p>
<button onClick={() =>
setCount(count + 1)}>
Increment
</button>
</div>
);
}
export default App;? useState in ReactLazy initialization in useState helps optimize performance by computing the initial state only when the component first renders, rather than on every render.
Normally, useState directly assigns an initial value:
const [count, setCount] = useState(0);
* Issue: If the initial value is expensive to compute (e.g., fetching data, performing calculations), it runs on every render, even if unnecessary.
* Lazy Initialization Fix: Pass a function to useState instead of a value:
const [count, setCount] = useState(() => expensiveCalculation());
Here, expensiveCalculation runs only once, during the initial render.
function Counter() {
function expensiveCalculation() {
console.log("Running expensive calculation...");
return 1000; // Imagine a complex operation
}
const [count, setCount] = useState(() => expensiveCalculation());
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(prev => prev + 1)}>Increment</button>
</div>
);
}
useState(() => expensiveCalculation()) ensures expensiveCalculation only runs once during the initial render.count is retrieved from React’s state instead of recalculating.0, "", or false).| Approach | Code Example | When to Use? |
|---|---|---|
| Normal Initialization | useState(0) |
For simple values |
| Lazy Initialization | useState(() => expensiveCalculation()) |
When computing the initial state is expensive |