reactjavascripttutorialfreshers
React Hooks Explained: useState, useEffect, useContext with Real Examples
Siva Prasad Galaba· Staff Engineer at Crunchyroll | Founder, CodeBegun·
Clear explanation of React hooks for beginners — useState, useEffect, useContext, useCallback, useMemo. With real code examples and common mistakes to avoid.
React Hooks are the feature that made React the dominant frontend framework. Before hooks (pre-2019), managing state and side effects required class components with lifecycle methods. Today, every modern React app uses functional components with hooks. This guide covers the hooks that appear in every fresher interview and every production app.
## What Are Hooks?
Hooks are functions that let you "hook into" React features — state, lifecycle, context — from inside functional components. They always start with `use`.
**Rules of Hooks (memorize these — they come up in interviews):**
1. Only call hooks at the top level of a component — not inside loops, conditions, or nested functions
2. Only call hooks from React functional components or custom hooks — not regular JavaScript functions
## useState — Managing Local State
`useState` is the most fundamental hook. It creates a piece of state within a component.
```jsx
const [value, setValue] = useState(initialValue);
```
### Basic counter:
```jsx
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
```
### Managing an object in state:
```jsx
function ProfileForm() {
const [form, setForm] = useState({ name: "", email: "", city: "" });
function handleChange(field, value) {
setForm({ ...form, [field]: value }); // spread existing, override the changed field
}
return (
<form>
<input value={form.name} onChange={(e) => handleChange("name", e.target.value)} />
<input value={form.email} onChange={(e) => handleChange("email", e.target.value)} />
</form>
);
}
```
**Critical mistake:** Never mutate state directly.
```jsx
// Wrong — React won't detect the change and won't re-render
state.name = "New Name";
// Correct — always create a new object/array
setForm({ ...form, name: "New Name" });
setItems([...items, newItem]);
```
## useEffect — Side Effects After Render
`useEffect` runs code after React renders the component. Use it for API calls, subscriptions, timers, and DOM manipulation.
```jsx
useEffect(() => {
// side effect code here
return () => {
// cleanup code here (runs before next effect or on unmount)
};
}, [dependencies]);
```
### Fetch data on mount:
```jsx
function StudentList() {
const [students, setStudents] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("/api/students")
.then((res) => {
if (!res.ok) throw new Error("Failed to fetch");
return res.json();
})
.then((data) => {
setStudents(data);
setLoading(false);
})
.catch((err) => {
setError(err.message);
setLoading(false);
});
}, []); // empty array = run once on mount
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{students.map((s) => <li key={s.id}>{s.name}</li>)}
</ul>
);
}
```
### Re-fetch when a value changes:
```jsx
function StudentsByCity({ city }) {
const [students, setStudents] = useState([]);
useEffect(() => {
fetch(`/api/students?city=${city}`)
.then(res => res.json())
.then(setStudents);
}, [city]); // re-runs whenever `city` prop changes
return <ul>{students.map(s => <li key={s.id}>{s.name}</li>)}</ul>;
}
```
### Effect with cleanup (important for interviews):
```jsx
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
return () => clearInterval(interval); // cleanup: clears the interval on unmount
}, []);
return <p>Running for {seconds} seconds</p>;
}
```
## useContext — Sharing State Without Prop Drilling
**Prop drilling** is when you pass props through many intermediate components that don't need the data — just to get it to a deeply nested child. `useContext` solves this.
### Create and use a theme context:
```jsx
import { createContext, useContext, useState } from "react";
// 1. Create the context
const ThemeContext = createContext("light");
// 2. Provide it at the top level
function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<Main />
</ThemeContext.Provider>
);
}
// 3. Consume it anywhere in the tree — no prop drilling
function Header() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<header style={{ background: theme === "dark" ? "#222" : "#fff" }}>
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
Toggle Theme
</button>
</header>
);
}
```
## useCallback — Memoizing Functions
`useCallback` returns a memoized version of a callback that only changes if one of its dependencies changes. Useful when passing functions as props to child components to prevent unnecessary re-renders.
```jsx
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// Without useCallback: this function is re-created on every render
// With useCallback: only re-created when count changes
const handleIncrement = useCallback(() => {
setCount(prev => prev + 1);
}, []); // no dependencies — function never changes
return (
<div>
<input value={text} onChange={e => setText(e.target.value)} />
<ChildButton onClick={handleIncrement} />
</div>
);
}
```
## useMemo — Memoizing Computed Values
`useMemo` caches the result of a computation. Use it when you have expensive calculations that shouldn't re-run on every render.
```jsx
function StudentStats({ students }) {
// Without useMemo: recalculates on every render, even when students hasn't changed
// With useMemo: only recalculates when students changes
const stats = useMemo(() => {
const total = students.length;
const placed = students.filter(s => s.isPlaced).length;
const avgSalary = students.reduce((sum, s) => sum + s.salary, 0) / total;
return { total, placed, avgSalary };
}, [students]);
return (
<div>
<p>Total: {stats.total}</p>
<p>Placed: {stats.placed}</p>
<p>Avg Salary: ₹{stats.avgSalary.toFixed(1)} LPA</p>
</div>
);
}
```
## useRef — Accessing DOM Elements
`useRef` holds a mutable reference that doesn't trigger re-renders when changed. Most commonly used to access DOM elements directly.
```jsx
function SearchInput() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current.focus(); // directly access the DOM element
}
return (
<div>
<input ref={inputRef} type="text" placeholder="Search..." />
<button onClick={focusInput}>Focus</button>
</div>
);
}
```
## Common Hook Mistakes in Interviews
**1. Calling hooks inside conditions:**
```jsx
// Wrong
if (isLoggedIn) {
const [user, setUser] = useState(null); // ❌ violates rules of hooks
}
// Correct
const [user, setUser] = useState(null); // always at top level
```
**2. Missing dependencies in useEffect:**
```jsx
// Wrong — effect uses `userId` but doesn't list it as dependency
useEffect(() => {
fetchUser(userId);
}, []); // ❌ ESLint will warn about this
// Correct
useEffect(() => {
fetchUser(userId);
}, [userId]); // ✓
```
**3. Directly mutating state:**
```jsx
// Wrong
const newList = students;
newList.push(newStudent);
setStudents(newList); // ❌ same reference, React won't re-render
// Correct
setStudents([...students, newStudent]); // ✓ new array
```
**4. Infinite loop in useEffect:**
```jsx
// Wrong — object/array created inline is a new reference each render
useEffect(() => {
fetchData(options);
}, [{ page: 1 }]); // ❌ new object every render → infinite loop
// Correct — primitive values, or memoize the object
useEffect(() => {
fetchData(options);
}, [page]); // ✓
```
---
These hooks are tested in 90% of React fresher interviews. Knowing the "why" behind each one — not just the syntax — is what separates candidates who get offers from those who don't. CodeBegun's program covers React from fundamentals to production patterns as part of the Java Full Stack curriculum. [Learn more →](/java-full-stack)
Siva Prasad Galaba
Staff Engineer at Crunchyroll | Founder, CodeBegun
Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaching the next generation to code the way the industry actually works.
