React Hooks Explained: useState and useEffect
SkillVeris Team
Engineering Team

React Hooks are functions that let function components use state and other React features that once required class components — the two most fundamental are useState and useEffect.
In this guide, you'll learn:
- useState declares a piece of state and returns the current value plus a setter function that triggers a re-render when called.
- useEffect runs side effects — data fetching, subscriptions, timers — after the component renders, controlled by a dependency array.
- The dependency array decides when an effect re-runs: empty means once on mount, listed values mean re-run when they change, omitted means every render.
- Return a cleanup function from useEffect to tear down subscriptions and timers and prevent memory leaks.
1What Are React Hooks?
React Hooks are special functions that let function components use state and lifecycle features that previously required class components. Introduced in React 16.8, they made classes largely unnecessary and are now the standard way to write React. The two you will reach for constantly are useState, for managing changing data, and useEffect, for running side effects.
Hooks work by letting React associate state and effects with each component instance across renders. Every hook name starts with use, and this naming is not just convention — it is how React's tooling identifies and enforces the rules that keep hooks working correctly.
2Managing State With useState
useState declares a single piece of state inside a component. You call it with the initial value, and it returns an array of two things: the current value and a function to update it. Calling the setter tells React to re-render the component with the new value. Array destructuring gives the pair readable names.
- import { useState } from 'react';
- const [count, setCount] = useState(0); // initial value 0
- setCount(count + 1); // updates and triggers a re-render
- setCount(prev => prev + 1); // functional update, safest for rapid changes
- const [user, setUser] = useState({ name: '', age: 0 }); // object state
💡Use the Functional Updater
When new state depends on the previous state, call setCount(prev => prev + 1). It avoids stale-value bugs when multiple updates are batched in the same render.
3State Updates Are Immutable
React state must be treated as immutable — never mutate it directly. To update an object or array, create a new one with the change applied, usually with the spread operator. Mutating in place does not trigger a re-render, because React compares references to decide what changed and a mutated object still has the same reference.
- setUser({ ...user, age: 30 }); // new object, correct
- user.age = 30; // mutation, wrong — no re-render
- setItems([...items, newItem]); // add to array immutably
- setItems(items.filter(i => i.id !== id)); // remove immutably
⚠️Do Not Mutate State
Directly changing a state object or array is a top React bug. React may not re-render, and even if it does, the behaviour becomes unpredictable. Always produce a new value.
4Side Effects With useEffect
useEffect runs code that reaches outside the render — fetching data, setting up subscriptions, starting timers, or manually touching the DOM. React runs the effect after the component renders to the screen. You pass it a function containing the effect and, as a second argument, a dependency array that controls when it re-runs.
- import { useEffect } from 'react';
- useEffect(() => {
- fetch(`/api/user/${id}`).then(/* ... */);
- }, [id]); // re-runs whenever id changes
- useEffect(() => { /* runs once on mount */ }, []); // empty deps
- useEffect(() => { /* runs after every render */ }); // no deps array
5Understanding the Dependency Array
The dependency array is the heart of useEffect, and getting it right is what separates smooth React from buggy React. It tells React which values the effect depends on, so React re-runs the effect only when one of them changes.
- [] (empty): the effect runs once after the first render — good for initial data loads.
- [a, b]: the effect re-runs whenever a or b changes between renders.
- No array at all: the effect runs after every single render — rarely what you want.
- Rule: include every value from the component that the effect reads, or you get stale data.
Cleanup Functions
If your effect sets up something ongoing — a timer, a subscription, an event listener — return a function that tears it down. React runs this cleanup before the next effect and when the component unmounts, preventing memory leaks and duplicate subscriptions.
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id); // cleanup on unmount or re-run
}, []);6The Rules of Hooks
Hooks come with two firm rules that React relies on to track state correctly. Breaking them leads to confusing bugs, which is why the official ESLint plugin enforces them automatically. Follow them and hooks just work.
- Only call hooks at the top level — never inside conditions, loops, or nested functions.
- Only call hooks from React function components or from custom hooks, not plain functions.
- Call the same hooks in the same order on every render so React can match state to hooks.
- Install eslint-plugin-react-hooks to catch violations and missing dependencies as you code.
🔑Why Order Matters
React tracks each hook by its call order, not by name. A hook hidden inside an if can shift that order between renders, scrambling which state belongs to which hook.
7Common Mistakes to Avoid
Most hook bugs trace back to a handful of recurring errors around dependencies, mutation, and effect placement.
- Omitting dependencies from useEffect, causing effects to read stale state.
- Mutating state directly instead of creating a new object or array.
- Forgetting a cleanup function, leaking timers and subscriptions across renders.
- Calling hooks conditionally, breaking the rules of hooks.
- Creating an infinite loop by updating state inside an effect that depends on that state.
8Key Takeaways
The core of useState and useEffect fits into a short list.
- useState stores changing data and returns a value plus a setter that re-renders.
- Treat state as immutable — always produce a new object or array.
- useEffect runs side effects after render, controlled by its dependency array.
- Empty deps run once; listed deps run on change; return cleanup for teardown.
- Follow the rules of hooks: top level only, same order every render.
9Frequently Asked Questions
Q: What is the difference between useState and useEffect? A: useState manages a piece of state inside a component and re-renders when it changes. useEffect runs side effects — like data fetching, subscriptions, or timers — after the component renders. useState is about data that changes; useEffect is about synchronising with things outside React.
Q: What does the dependency array in useEffect do? A: It tells React when to re-run the effect. An empty array runs it once after mount, an array of values re-runs it whenever any of those values change, and omitting the array entirely runs the effect after every render. You should list every value the effect reads to avoid stale data.
Q: Why should I not mutate state directly? A: React decides whether to re-render by comparing references. Mutating a state object or array in place keeps the same reference, so React may not detect the change and will not re-render. Always create a new object or array, typically with the spread operator, when updating state.
Q: Can I call hooks inside an if statement? A: No. Hooks must be called at the top level of a component in the same order on every render, because React tracks them by call order. Calling a hook conditionally changes that order and breaks React's internal state tracking. Put the condition inside the hook instead.
Related Reading
Get The Print Version
Download a PDF of this article for offline reading.
About the Publisher
SkillVeris Team
Engineering Team
Our engineering writers turn abstract code concepts into hands-on, project-driven learning experiences.
View all postsRelated Posts
Never miss an update
Get the latest tutorials and guides delivered to your inbox.
No spam. Unsubscribe anytime.