React Hooks
React Hooks
Level 1 — Core Concepts & Architecture Special built-in functions that let functional React components tap into state, lifecycles, and context.
1. Prerequisites
- React Components — The building blocks that hooks add features to.
2. Term Category
React Server Component (Client-Side React Hooks): React Hooks (useState, useEffect, useContext) enable state management and side effects inside Client Components.
3. Explanation
Environment Context
- Client Only (Hooks only execute in the browser environment, not on Server Components).
(1) Design Motivation — "Why did we design this?"
In early versions of React, components were divided into class components and functional components. Functional components were simple, stateless presentation functions that accepted props and returned JSX. If a developer needed to track state (like a toggle flag) or trigger side effects (like fetching data after mount), they had to refactor the entire function into a complex class component (using this.state and lifecycle methods like componentDidMount).
React 16.8 introduced Hooks to solve this. Hooks allow you to use state and other React features without writing a class component. This makes code clean, shareable, and easy to maintain. In Next.js, hooks are essential for building interactive Client Components.
(2) Core Concept — State and Effect Hooks
The two most foundational hooks are useState and useEffect:
'use client'; // Required in Next.js to use hooks!
import React, { useState, useEffect } from 'react';
export default function ClickCounter() {
// 1. Declare state variable 'count' initialized to 0
const [count, setCount] = useState<number>(0);
// 2. Declare side effect hook to update tab title when count changes
useEffect(() => {
document.title = `Clicked ${count} times`;
}, [count]); // Dependency array: only re-run effect when count changes
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
(3) The Strict Rules of Hooks
To ensure React links hooks correctly to component state between renders, they must follow two strict rules:
- Only Call Hooks at the Top Level: Do not call hooks inside loops, conditions (
ifblocks), or nested functions. React relies on the execution order of hook calls to remain identical on every render. - Only Call Hooks from React Functions: Call hooks only from React functional components or custom hooks, never from standard helper functions.
4. Common Mistakes & Pitfalls
Mistake 1: Calling a hook conditionally
The mistake: Putting a hook call inside an if block:
// BAD: Violates the rules of hooks by rendering conditionally!
export default function ConditionalStatus({ isLoggedIn }: { isLoggedIn: boolean }) {
if (!isLoggedIn) {
return <p>Please log in.</p>;
}
// Hook called after a return statement and inside condition
const [data, setData] = useState(null);
return <div>Welcome!</div>;
}
Why it's wrong: React keeps track of state variables by their execution index sequence. If a hook call is skipped during a render pass because a condition is met, all subsequent hook values shift, corrupting the component's state.
Golden Rule: Always declare hooks unconditionally at the very top of your functional components.
Mistake 2: Invoking React Hooks inside React Server Components (RSC)
The mistake: Writing const [state, setState] = useState(0) inside a Server Component.
Why it's wrong: React hooks (useState, useEffect, useRef) run ONLY in the browser environment. Server Components execute on the server and do not support stateful hooks. Add 'use client'.
Incorrect:
// Server Component (default)
import { useState } from 'react';
export default function Page() {
const [count, setCount] = useState(0); // ❌ Error: Hooks only work in Client Components!
}
Fix:
'use client'; // Convert to Client Component
import { useState } from 'react';
export default function Page() {
const [count, setCount] = useState(0);
}
Mistake 3: Calling React Hooks Conditionally Inside if Statements or Loops
The mistake: Writing if (isLoggedIn) { useEffect(() => {}, []); }.
Why it's wrong: React relies on the strict call order of hooks across re-renders. Calling hooks conditionally breaks React's internal fiber hook index, causing fatal runtime crashes. Follow Rules of Hooks.
Incorrect:
if (condition) {
useEffect(() => {}, []); // ❌ Violation of Rules of Hooks!
}
Fix:
useEffect(() => {
if (condition) {
// Place condition INSIDE the hook callback
}
}, [condition]);
5. Practice Exercises
Exercise 1: Managing Local Component State with useState()
Scenario:
Create an interactive input field component tracking search state using useState().
Requirements:
- Implement
useState("")inside"use client"component.
Answer
Implementation
"use client";
import { useState } from "react";
export default function SearchBar() {
const [query, setQuery] = useState("");
return (
<div className="p-4">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Type to search..."
className="p-2 border rounded w-full"
/>
<p className="mt-2 text-sm text-gray-600">Active Query: {query}</p>
</div>
);
}
Technical Explanation
useState(initialValue)returns a stateful value and a setter function to update it.- Mutating state via setter function triggers component re-rendering.
- React hooks require marking the component file with
"use client".
Exercise 2: Managing Side Effects with useEffect()
Scenario:
Subscribe to browser window resize events using useEffect() and clean up the listener on unmount.
Requirements:
- Add event listener in
useEffect()and return cleanup function.
Answer
Implementation
"use client";
import { useState, useEffect } from "react";
export default function WindowSize() {
const [width, setWidth] = useState<number>(0);
useEffect(() => {
// Client-side window access
setWidth(window.innerWidth);
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
// Cleanup listener on component unmount
return () => window.removeEventListener("resize", handleResize);
}, []);
return <p>Window Width: {width}px</p>;
}
Technical Explanation
useEffect(callback, dependencies)executes side effects after component rendering.- Returning a function from the callback performs cleanup when the component unmounts or dependencies update.
- Empty dependency array
[]runs the effect once on mount.
Exercise 3: Optimizing Derived Values with useMemo()
Scenario:
Memoize an expensive array filter operation using useMemo() to prevent unnecessary recalculations.
Requirements:
- Wrap calculation in
useMemo().
Answer
Implementation
"use client";
import { useState, useMemo } from "react";
export default function FilteredList({ items }: { items: string[] }) {
const [filter, setFilter] = useState("");
const filteredItems = useMemo(() => {
return items.filter((item) => item.toLowerCase().includes(filter.toLowerCase()));
}, [items, filter]);
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
<ul>
{filteredItems.map((item, idx) => (
<li key={idx}>{item}</li>
))}
</ul>
</div>
);
}
Technical Explanation
useMemo(() => value, [deps])caches the result of an expensive calculation between renders.- Re-evaluates ONLY when dependency array values change.
- Prevents CPU performance bottlenecks during component re-renders.
6. Related Terms
- Client Components (
"use client") — Next.js files marked with'use client'that enable hooks. - React Components — The visual wrapper surrounding hooks.
- React
useEffectHook — Related concept: ReactuseEffectHook.
7. Key Takeaways
- Hooks allow functional components to maintain state and handle lifecycle events.
useStatemanages local variables;useEffectmanages asynchronous side effects.- Hooks must be declared unconditionally at the very top of functional components.
- In Next.js, you must add the
'use client'directive to files containing hooks. - Hooks cannot run inside Server Components.