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:
useState
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
useEffect
import { 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>;
}
useMemo
import { useMemo, useState } from "react";
function ExpensiveCalculation({ num }) {
const squaredNumber = useMemo(() => {
console.log("Calculating...");
return num * num;
}, [num]);
return <p>Squared: {squaredNumber}</p>;
}
useCallback
import { 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>
);
}
useRef
import { useRef, useEffect } from "react";
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
useImperativeHandle
React.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>;
}
useReducer
useState
, 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>
);
}
useLayoutEffect
useEffect
, 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>;
}
useDebugValue
import { 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
Provider
Wrap your components inside a Provider to supply the context value.
function App() {
return (
<ThemeContext.Provider value="dark">
<ChildComponent />
</ThemeContext.Provider>
);
}
useContext
Instead 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.
fetch
import { 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
:useEffect
function 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
:useEffect
useEffect(() => {
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 setTimeout
Debouncing 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.
useEffect
import { 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 |