Hooks are the feature that made modern React what it is. Introduced in React 16.8, they let plain function components hold state, run side effects, and reuse logic — abilities that previously required writing verbose class components. Today virtually all new React code is written with function components and hooks.
If you have read what React is, you know a component is a function that returns JSX. Hooks are the functions you call inside that component to give it memory and behavior. This guide focuses on the two you will use constantly, useState and useEffect, plus the rules that keep them working correctly.
Why hooks exist
Before hooks, a component that needed to remember anything — a counter value, whether a menu is open, form input — had to be a class with this.state, a constructor, and lifecycle methods like componentDidMount. This was boilerplate-heavy, confusing for beginners (this binding bugs were everywhere), and made reusing logic awkward.
Hooks removed all of that. A function component plus useState and useEffect can do everything a class could, with far less code and no this. That is why every current tutorial, including this one, teaches hooks first and treats classes as legacy.
useState: giving a component memory
A component re-runs its function every time it renders. Any normal variable inside it would reset on each render — so how does a counter remember its value? That is exactly what useState provides: a value that survives across renders, plus a function to update it.
import { useState } from "react";
function LikeButton() {
const [likes, setLikes] = useState(0);
return (
<button onClick={() => setLikes(likes + 1)}>
{likes} likes
</button>
);
}
export default LikeButton;
Read the destructuring carefully. useState(0) returns an array with two items: the current value (likes) and a setter function (setLikes). The argument 0 is the initial value, used only on the first render.
The crucial rule: never assign to state directly. Writing likes = likes + 1 does nothing useful — React will not notice and the screen will not update. You must call the setter, setLikes, which updates the value and tells React to re-render.
Common mistake: Mutating state directly, especially with objects and arrays.
todos.push(newItem)followed bysetTodos(todos)often fails to re-render because the array reference did not change. Always create a new value:setTodos([...todos, newItem]). Treat state as read-only.
Updating state based on previous state
When your new state depends on the old state, prefer the updater function form of the setter. Compare these:
// Risky if called multiple times in one event
setCount(count + 1);
// Safe: React passes you the latest value
setCount((prev) => prev + 1);
The updater form guarantees you are working with the most recent value, which matters when multiple updates happen close together. This is a favorite interview follow-up, so understand why: state updates are batched, and the plain form can use a stale count.
useEffect: running side effects
Rendering should be pure — it just computes JSX from props and state. But real apps also need to do things: fetch data from an API, set up a timer, subscribe to events, or update the document title. These are side effects, and they belong in useEffect.
import { useState, useEffect } from "react";
function Clock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(id); // cleanup
}, []);
return <p>Current time: {time.toLocaleTimeString()}</p>;
}
export default Clock;
Three parts matter here. The effect function runs after render. The cleanup function it returns (here clearInterval) runs before the effect re-runs and when the component unmounts — this is how you avoid memory leaks. And the dependency array [] controls when the effect runs.
The dependency array
The second argument to useEffect decides how often it runs:
| Dependency array | When the effect runs |
|---|---|
| Omitted | After every render |
[] (empty) |
Only once, after the first render |
[a, b] |
When a or b changes between renders |
Getting this wrong is the number one source of useEffect bugs. Forgetting a dependency your effect uses leads to stale data; including an object that is recreated every render leads to an infinite loop. A full walkthrough of these two hooks, including a real data-fetching example, lives in React hooks explained: useState and useEffect.
Pro tip: When fetching data in
useEffect, put the URL or ID in the dependency array so the fetch re-runs when it changes. And always handle the case where the component unmounts mid-request, or you will see "can't update state on an unmounted component" warnings.
The two rules of hooks
Hooks rely on being called in the same order on every render. React tracks them by call order, not by name, so breaking that order corrupts which state belongs to which hook. This gives us two non-negotiable rules:
- Only call hooks at the top level. Never inside
if,for,while, or nested functions. If you need conditional behavior, put the condition inside the hook, not around it. - Only call hooks from React functions. That means function components or other custom hooks — never from plain JavaScript functions or event handlers.
// ❌ Wrong: hook inside a condition
if (loggedIn) {
const [name, setName] = useState("");
}
// ✅ Right: hook at top level, condition inside logic
const [name, setName] = useState("");
if (loggedIn) {
// use name here
}
Most projects enforce these automatically with an ESLint plugin, and I strongly recommend leaving that lint rule on. It catches the mistakes before they become confusing runtime bugs.
Other core hooks to know
useState and useEffect cover most beginner needs, but three more show up constantly:
- useContext reads shared data (theme, current user) without passing props through every level. It is the foundation of lightweight global state, covered in the state management overview.
- useRef holds a mutable value that does not trigger re-renders — perfect for referencing a DOM element or storing a timer id.
- useMemo and useCallback cache expensive calculations and function identities to avoid unnecessary work. Reach for them only when you have a real performance need, not by default.
Hooks vs class lifecycle methods
If you read older React tutorials, you will see lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. Hooks replace all three with a single useEffect, and understanding the mapping helps you read legacy code and answer interview questions.
| Class lifecycle | Hooks equivalent |
|---|---|
componentDidMount |
useEffect(() => {...}, []) |
componentDidUpdate |
useEffect(() => {...}, [dep]) |
componentWillUnmount |
the cleanup function returned by useEffect |
The mental shift is important: with classes you thought in terms of lifecycle moments, but with hooks you think in terms of synchronizing with data. Instead of "run this when the component mounts", you say "keep this effect in sync with these values". Once that reframing lands, the dependency array stops feeling arbitrary and starts feeling like a declaration of what the effect depends on.
Custom hooks: reusing logic
Once you understand the built-in hooks, you can build your own. A custom hook is simply a function whose name starts with use and that calls other hooks. This lets you extract stateful logic and reuse it:
import { useState, useEffect } from "react";
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return width;
}
Any component can now call const width = useWindowWidth() and get live updates. Custom hooks are how experienced teams keep components clean and avoid copy-pasting logic.
Where to go next
Hooks are the daily language of React work, so practice them until useState and useEffect are muscle memory. Build a small app — a timer, a search box that fetches results, a to-do list — and you will hit every concept above naturally.
For interview preparation specifically, the React hooks interview questions page drills the exact questions freshers face, and hooks are also central to the broader React interview questions guide. Your natural next topic is scaling state beyond a single component, which the React state management overview explains from Context up to dedicated libraries.
Frequently Asked Questions
What are React hooks in simple terms?
What is the difference between useState and useEffect?
What are the rules of hooks?
Why is my useEffect running twice?
Can I create my own hooks?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

