Before 2019, giving a component state or lifecycle behavior meant writing a verbose Class Component. Hooks changed that by letting plain functions 'hook into' React's features directly ā but they come with a small set of strict rules that this lesson covers in depth.
1The Dark Ages
Before React 16.8, released in 2019, functional components were purely 'dumb' ā they could accept props and render UI, but they had no way to hold state or hook into lifecycle methods. Any component that needed state was forced into a verbose Class Component with a constructor, this.state, and manual method binding.
That verbosity wasn't a stylistic choice; it was the only option available before Hooks existed.
// The old way (Class)
class App extends React.Component { ... }
// The new way (Hooks)
function App() { ... }Class Components
The old paradigm.
2The Problem with Classes
Class Components had three major recurring flaws: the this keyword confused beginners and required manual binding in constructors, lifecycle methods like componentDidMount forced unrelated pieces of logic to be jammed together into a single method, and sharing stateful logic between components was incredibly difficult without patterns like render props or higher-order components.
These pain points were the direct motivation for introducing Hooks.
import { useState, useEffect } from 'react';Spaghetti Code
Tangled lifecycles.
3The Hook Revolution
React 16.8 introduced Hooks, which let functional components 'hook into' React's state and lifecycle features from entirely within a plain, clean JavaScript function ā no constructors, no this, and no manual binding required.
A function component can now hold its own memory with something as simple as const [count, setCount] = useState(0), something that previously required an entire class definition.
function App() {
const [val, setVal] = useState(0); // ā
Top Level
if (condition) {
// ā WRONG: useState(10);
}
}The Revolution
Functions level up.
4What are Hooks?
At their core, Hooks are just regular JavaScript functions imported from the react library, like import { useState, useEffect } from 'react'. They give ordinary UI functions superpowers ā useState for memory, useEffect for side effects ā that a plain function couldn't otherwise have.
There's nothing magical happening syntactically; a Hook is just a function call that React recognizes and treats specially internally.
// ā
React Component
function MyComp() { useHook(); }
// ā Regular JS function
function utils() { useHook(); }Imports
Grabbing the tools.
5The
By convention and strict rule, every Hook name must start with the word use ā useState, useEffect, useAuthUser, and so on. This isn't just a style preference; it's what allows React's linter tooling to automatically detect Hook calls and enforce the Rules of Hooks.
A function named getState() or fetchData() that internally calls other Hooks won't be recognized as a Hook itself, so the linter can't verify it's being used correctly ā which is exactly why the use prefix is mandatory rather than optional.
const { data } = useMyCustomDataFetcher();React Component Preview
6Rule #1: Top Level Only
Hooks must only be called at the top level of a React function ā never inside loops, conditions like if statements, or nested functions. A Hook call has to be unconditional, meaning it runs on every single render, no exceptions.
Writing if (isOnline) { useState(null) } is a fatal error precisely because it makes the Hook conditional, even though the code looks harmless at a glance.
// Stable Hook Order = Predictable AppRule #1
Top Level Only.
7Why Top Level?
React enforces the top-level rule because it tracks Hook state internally by the exact order Hooks are called in, not by name. Each useState or useEffect call corresponds to a specific position in an internal array, like index 0, 1, 2, and so on.
If a Hook is skipped on one render because it sat inside a conditional that turned false, every subsequent Hook call shifts down one slot in that internal array, and React ends up reading the wrong stored value for each one ā silently corrupting the component's state.
<h1>Hook Expert!</h1>The Engine
Call Order Matters.
8Under the Hood: A Linked List
Internally, React attaches a linked list of 'memory cells' to each component instance (technically its Fiber node). Every Hook call reads or writes the next cell in that list, advancing an internal cursor ā the 'array index' framing is a simplification of the real data structure, but the consequence is identical: skip a Hook conditionally, and every later Hook call reads the wrong cell.
// Simplified mental model
fiber.memoizedState = cell0 -> cell1 -> cell2 -> ...The Real Data Structure
A linked list per component.
9The ESLint Plugin Catches Violations
The official eslint-plugin-react-hooks package ships two rules: 'rules-of-hooks', which flags conditional or out-of-order Hook calls at build time, and 'exhaustive-deps', which flags missing dependencies in useEffect, useMemo, and useCallback. Both rules only work because every Hook name starts with use, which is exactly what lets the linter recognize a Hook call in the first place.
// eslint-plugin-react-hooks catches this automatically:
if (isOnline) {
useState(null); // React Hook "useState" is called conditionallyLint Error
Caught before runtime.
10A Catalog of Built-in Hooks
React ships several built-in Hooks, each solving a distinct problem: useState holds a single piece of memory, useEffect synchronizes with something outside React, useContext reads a value from a Provider above without prop drilling, useRef holds a mutable value that never triggers a re-render, useReducer centralizes complex state transitions, and useMemo/useCallback memoize a value or function to preserve reference identity across renders.
useState() // memory
useEffect() // sync with the outside world
useContext() // read shared data
useRef() // mutable, non-rendering value
useReducer() // complex state transitionsThe Toolbox
Each hook, one job.
11Step-by-Step Breakdown
The Dark Ages. Before React 16.8 (released in 2019), Functional Components were 'dumb'. They couldn't hold state or use lifecycle methods. If you needed state, you were forced to write verbose Class Components.
The Problem with Classes. Classes had three major flaws: 1) The 'this' keyword confused beginners. 2) Lifecycle methods (componentDidMount) forced unrelated logic to be grouped together. 3) Sharing logic between components was incredibly difficult.
The Hook Revolution. In React 16.8, Hooks were introduced. Hooks allow Functional Components to 'hook into' React state and lifecycle features from entirely within a simple, clean Javascript function.
What are Hooks?. At their core, Hooks are just regular Javascript functions imported from the 'react' library. They give your UI functions superpowers like memory (useState) and the ability to cause side effects (useEffect).
The 'use' Prefix. By convention and strict rule, every single Hook must start with the word 'use'. This prefix allows React's linter tools to automatically check for bugs and enforce the Rules of Hooks.
Which of the following function names indicates to the React Linter that it is a Hook?
- āgetAuthenticationData()
- āuseAuthenticationData()
Rule #1: Top Level Only. CRITICAL: You must only call Hooks at the TOP LEVEL of your React function. Never call them inside loops, conditions (if statements), or nested functions. They must be unconditional.
Why Top Level?. Why this strict rule? Because React tracks Hook state by the *order* they are called. If a Hook is inside an if statement that suddenly becomes false, a Hook is skipped. This completely ruins the array index React uses internally, breaking the app.
Can you call useState or useEffect inside a for loop or an if statement?
- āYes, as long as the condition never changes
- āNo, they must always be at the top level
Under the Hood: A Linked List. Internally, React attaches a linked list of 'memory cells' to each component instance (technically its Fiber node). Every Hook call reads or writes the next cell in that list, advancing an internal cursor. The 'array index' idea is a simplification ā the real data structure is a linked list ā but the consequence is identical: skip a Hook conditionally, and every later Hook reads the wrong cell.
The ESLint Plugin Catches Violations. You rarely have to remember the Rules of Hooks by pure discipline ā the official eslint-plugin-react-hooks package ships two rules: 'rules-of-hooks', which flags conditional or out-of-order Hook calls at build time, and 'exhaustive-deps', which flags missing dependencies in useEffect, useMemo, and useCallback. Both rules only work because every Hook name starts with 'use', which is exactly what lets the linter recognize a Hook call in the first place.
Rule #2: React Functions Only. Rule #2: Only call Hooks from inside React Function Components, or from inside Custom Hooks. You cannot call them from regular utility Javascript functions.
Is it valid to call a Hook inside a standard Javascript helper function like formatDate(date)?
- āYes, hooks can run anywhere in JS
- āNo, hooks only work inside React components
A Catalog of Built-in Hooks. React ships several built-in Hooks, each solving a distinct problem: useState holds a single piece of memory; useEffect synchronizes with something outside React; useContext reads a value from a Provider above without prop drilling; useRef holds a mutable value that never triggers a re-render; useReducer centralizes complex state transitions; useMemo and useCallback memoize a value or function to preserve reference identity. Each has its own dedicated lesson ahead.
Custom Hooks. Because Hooks are just functions, you can write your own! 'Custom Hooks' are just JS functions that start with 'use' and call other hooks. This allows you to extract and reuse complex stateful logic effortlessly.
The Death of 'this'. Thanks to Hooks, you will almost never need to write a Class Component or use the this keyword in modern React. The community has fully embraced Functional Components.
A Predictable App. By strictly following the Rules of Hooks, you ensure that React's engine can predictably preserve your state between every single re-render of the DOM.
Hook Expert. Brilliant! You've learned the fundamental rules of modern React. You understand why we abandoned classes and the strict rules required to use Hooks properly. Let's dive deeper!
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Hooks Don't Change What Markup a Component Renders
Whether a component is written as a class or with Hooks has no bearing on accessibility ā the underlying JSX still needs semantic elements, labels, and ARIA attributes exactly as before; Hooks only change how state and side effects are organized in the source code.
2Effect-Driven Focus Management Still Needs Explicit Handling
If a `useEffect` opens a modal or navigates the user to new content, the effect should also explicitly move keyboard focus to that content ā React doesn't do this automatically just because state changed.
SEO Implications
- 1
Hooks Are a Client-Side Implementation Detail Invisible to Crawlers
Whether a component is built with Hooks or as a legacy class has zero effect on the HTML a search engine sees ā Hooks affect component internals, not the rendered markup structure or its SEO value.
- 2
Custom Hooks Encourage Consistent Data-Fetching Patterns Across Pages
Extracting fetch logic into a shared custom Hook like `useArticleData()` makes it easier to guarantee that every page using it renders the same critical metadata and content consistently, reducing accidental inconsistencies that could hurt indexing.
Best Practices
Extract Repeated Stateful Logic Into a Custom Hook
If multiple components need the same combination of `useState` and `useEffect` logic ā like tracking window width or a data fetch ā wrap it in a custom Hook (a function starting with `use`) instead of duplicating the logic in each component.
Never Call Hooks Conditionally, Even for 'Obviously Safe' Conditions
Even if a condition never changes at runtime, wrapping a Hook call in an `if` statement or early return breaks React's ability to reliably track Hook order ā always call every Hook unconditionally at the top of the function.
Frequent Bugs
React throws 'Rendered more hooks than during the previous render' after adding a conditional around a Hook call.
A Hook was placed inside an `if` statement or after an early return, so it wasn't called on every render. Move all Hook calls to the unconditional top level of the component, and put any conditional logic inside the Hook itself instead.
Calling a custom function that internally uses useState throws 'Invalid hook call' when used outside a component.
Hooks ā including custom Hooks ā can only be called from the body of a React function component or from another custom Hook, never from a plain JavaScript utility function. Rename the function to start with `use` only if it's genuinely meant to be called from a component.
Real-World Examples
A Custom Hook for Tracking Window Width
Instead of duplicating a resize event listener and useState pair in every component that needs the viewport width, a single useWindowWidth custom Hook centralizes that logic and can be reused anywhere.
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handler = () => setWidth(window.innerWidth);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
return width;
}