We’re diving into two of the most essential React hooks: useState and useEffect. Whether you’re a beginner or preparing for job interviews.
1. What are React Hooks?
React Hooks are special functions that let you use React features like state and lifecycle methods in functional components. Before hooks, we relied on class components, but hooks have simplified development and made code more readable.
2. useState
- The useState hook is used to add state to functional components. It returns two things: the current state and a function to update that state.
Example
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
in this example we are simply setting the count with the help of useState on click event. When we will click the button it will increase the count with 1.
Common Mistakes:
"Updating state directly: count++ — this won't work! Always use the setState function."
"Using useState outside a functional component. Hooks must be called within the component body."
Inside Another Function or Conditional Block (Not Allowed)
React hooks must be called in the same order on every render. If you try to call useState conditionally or inside a nested function, React can't guarantee this, leading to errors.
Example: Incorrect Usage
function Counter() {
if (true) { // This is a conditional block
const [count, setCount] = useState(0); // ❌ This will throw an error
}
return <div>Invalid Usage</div>;
}
Error Message:
"React Hook 'useState' is called conditionally. React Hooks must be called in the exact same order in every component render."
3. useEffect
useEffect is a React hook that handles side effects in functional components, like data fetching, subscriptions, or manually updating the DOM.
Syntax:
useEffect(() => {
// Your side effect code here
return () => {
// Cleanup code here (optional)
};
}, [dependencies]);
The dependencies array controls when the effect runs:
Empty ([]): Runs only once, when the component mounts.
No array: Runs after every render.Specified dependencies: Runs only when those values change.
Example: Timer with useEffect
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
return () => clearInterval(interval); // Cleanup the interval on unmount
}, []); // Empty array ensures this runs only once
return <h1>Seconds: {seconds}</h1>;
}
export default Timer;
-
How it works:
- The useEffect runs after the component mounts, setting up an interval to update seconds.
- The cleanup function clears the interval when the component unmounts, preventing memory leaks.
Common Mistakes with useEffect
- Missing Dependencies: If you forget to include variables in the dependency array, the effect might not behave as expected.
useEffect(() => {
console.log(data); // `data` should be in the dependency array
}, [data]);
- Infinite Loops: Updating state inside an effect without proper dependencies can cause the effect to run infinitely:
useEffect(() => {
setCount(count + 1); // This causes an infinite loop
});
Advanced Use Cases
- Updating Objects in useState When working with objects, always copy the previous state using the spread operator to avoid overwriting unintended properties.
const [user, setUser] = useState({ name: '', age: 0 });
function updateName() {
setUser((prevUser) => ({ ...prevUser, name: 'John' }));
}
- Conditional Effects Only run an effect if a condition is met.
useEffect(() => {
if (someCondition) {
console.log('Condition met!');
}
}, [someCondition]); // Runs when `someCondition` changes
- Fetching Data A common use of useEffect is fetching data from an API.
useEffect(() => {
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
console.log(result);
}
fetchData();
}, []); // Runs only once
Job Interview Questions
What are hooks? Can they be used in class components?
Hooks are functions that let you use React features in functional
components. They cannot be used in class components.What happens if you don’t provide a dependency array in useEffect?
The effect runs after every render, potentially leading to
performance issues or infinite loops.How do you clean up effects?
By returning a cleanup function from useEffect:
useEffect(() => {
const interval = setInterval(() => console.log('Running'), 1000);
return () => clearInterval(interval); // Cleanup
}, []);
- Why do we use functional updates in useState? To ensure the state is updated based on the most recent value when the updates depend on the previous state:
setState((prevState) => prevState + 1);
There is a question related to this concept asked commonly in interviews.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1); // Updates count by +1
setCount(count + 1); // Still uses the same count value (not updated yet)
setCount(count + 1); // Again, uses the same count value
};
return (
<div>
<h1>Count: {count}</h1>
<button onClick={incrementCount}>Increment</button>
</div>
);
}
export default Counter;
Let's say if we run this code what you expect to happen. The thing is here you will think if my current count is 1 then it will be 4 after clicking the button but in reality it gonna update the count by just 1 that is count will become 2.
Let's understand this, everytime we do setState(count+1)
. It is using the same count not the currently updated count because the count will update only at the time of rerendering. When you update a state in React like setState(count + 1)
multiple times in a row, the updates do not immediately reflect in the current render. Instead, React batches state updates for performance, and each setState call uses the current value of the state at the time of its execution.
solution:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount((prevCount) => prevCount + 1); // Uses the latest value of count
setCount((prevCount) => prevCount + 1); // Updates based on the new value
setCount((prevCount) => prevCount + 1); // Updates again based on the latest value
};
return (
<div>
<h1>Count: {count}</h1>
<button onClick={incrementCount}>Increment</button>
</div>
);
}
export default Counter;
When using the functional form of setState, React ensures that each update is based on the most recent state, even if multiple updates are queued.
- What Happens Without Clearing the Interval?
Interval Keeps Running:
When you set an interval using setInterval, the JavaScript runtime keeps executing the callback function at the specified time interval, regardless of whether the component is still in the DOM.Access to Unmounted Component:
The interval callback may try to update the state or interact with the DOM of the component that has already been unmounted. Since the component no longer exists, this leads to warnings or errors.
Example warning in React:
Can't perform a React state update on an unmounted component.
- Memory Leak: Since the interval is still running and holding a reference to the callback function, memory associated with the component (e.g., variables, state) cannot be garbage collected. This increases memory usage over time and can degrade application performance.
Top comments (0)