React’s useState and useEffect hooks are essential tools for managing state and side effects in functional components. However, they serve distinct purposes, and knowing when to use each—or when to avoid them entirely—can make your code cleaner and more efficient.
This guide covers:
useState
and useEffect
douseState
vs. useEffect
vs. local variablesThe useState hook manages state—data that persists across renders and triggers re-renders when updated. It’s ideal for tracking component-specific data like user inputs or UI toggles.
const [count, setCount] = useState(0);
The useEffect hook handles side effects—operations that interact with the outside world (e.g., APIs, DOM updates) or need to run at specific lifecycle moments (mount, update, unmount).
useEffect(() => {
fetch('https://api.example.com/data')
.then(res => res.json())
.then(setData);
}, [count]);
Sometimes, you don’t need either hook. Local variables or event handler logic can suffice for transient or render-specific computations.
function Component() {
const temporaryValue = Math.random(); // No need for useState
return <p>{temporaryValue}</p>;
}
graph TD
A[What do I need?] --> B{Does it change and affect UI?}
B -->|Yes| C[Use useState]
B -->|No| D{Does it interact with external systems, or need to run at specific lifecycle moments?}
D -->|Yes| E[Use useEffect]
D -->|No| F[Use local variables or event logic]
function Toggle() {
const [isOn, setIsOn] = useState(false);
return <button onClick={() => setIsOn(!isOn)}>{isOn ? 'On' : 'Off'}</button>;
}
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(res => res.json())
.then(setData);
}, []); // Runs once on mount
return <div>{data ? data.message : 'Loading...'}</div>;
}
function Calculator() {
const handleAdd = () => {
const a = 5; // Local variable, no state needed
const b = 10;
alert(a + b);
};
return <button onClick={handleAdd}>Add</button>;
}
Feature | useState | useEffect |
---|---|---|
Purpose | Manage state | Handle side effects |
Triggers Render | Yes, when state updates | No, runs after render |
Runs When | On state change | On mount/update/unmount |
Dependency | None | Optional dependency array |
Use Case | UI-related data | External interactions |
graph LR
A[Component Lifecycle] --> B[Render]
B --> |useState| C[State Updates]
C --> B
B --> |useEffect| D[Side Effects]
D --> E[External Systems]
E --> |May update state| C
function Counter() {
const [count, setCount] = useState(0); // State for UI
useEffect(() => {
document.title = `Count: ${count}`; // Side effect
}, [count]); // Runs when count changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
count
for the UI.function Form() {
const [input, setInput] = useState(''); // State for input
const isValid = input.length > 3; // Local variable, no hook needed
return (
<div>
<input value={input} onChange={e => setInput(e.target.value)} />
<p>{isValid ? 'Valid' : 'Too short'}</p>
</div>
);
}
input
for the form.isValid
is computed during render, no need for state or effects.function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(interval); // Cleanup
}, []); // Runs once on mount
return <p>Seconds: {seconds}</p>;
}
seconds
for the UI.useState
.useEffect
.graph TD
A[Data Need] --> B{Affects UI?}
B -->|Yes| C{Changes Over Time?}
C -->|Yes| D[useState]
C -->|No| E[Local Variable]
B -->|No| F{External System?}
F -->|Yes| G[useEffect]
F -->|No| H[Local Logic]