Hook Inside Condition
Calling hooks inside if blocks breaks hook order.
Hooks are functions provided by React that let functional components use state, run side effects, work with references, and reuse logic. Before hooks, these features were mostly done in class components. Hooks made React code cleaner and easier to split into small reusable parts.
Before hooks:
Hooks improved this by making it easier to:
A hook is a normal JavaScript function with special behavior in React. You call hooks inside React function components or inside custom hooks.
const [count, setCount] = useState(0);React tracks hooks by call order on every render. If the order changes, React can connect the wrong state to the wrong hook.
On each render, React runs your component function from top to bottom.
useState stores local component state.
const [state, setState] = useState(initialValue);open, loading, error).useReducer.import { useState } from "react";
function Counter() { const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;}useEffect runs side-effect code after React updates the screen.
useMemo if expensive.| Dependency array | Behavior |
|---|---|
[] | Runs once after mount |
[x] | Runs when x changes |
| no array | Runs after every render |
import { useEffect, useState } from "react";
function Clock() { const [time, setTime] = useState(new Date());
useEffect(() => { const id = setInterval(() => setTime(new Date()), 1000);
return () => { clearInterval(id); }; }, []);
return <p>{time.toLocaleTimeString()}</p>;}useRef stores a mutable value that survives re-renders without causing re-renders.
Changing ref.current does NOT re-renderimport { useRef } from "react";
function FocusField() { const inputRef = useRef(null);
return ( <> <input ref={inputRef} /> <button onClick={() => inputRef.current?.focus()}>Focus</button> </> );}This example mixes controlled state and useRef to create a practical form flow: auto-focus, reset, and validation focus.
import { FormEvent, useRef, useState } from "react";
export default function SignupForm() { const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [error, setError] = useState("");
const nameRef = useRef<HTMLInputElement | null>(null); const emailRef = useRef<HTMLInputElement | null>(null);
function handleSubmit(e: FormEvent) { e.preventDefault(); setError("");
if (!name.trim()) { setError("Name is required"); nameRef.current?.focus(); return; }
if (!email.includes("@")) { setError("Enter a valid email"); emailRef.current?.focus(); return; }
console.log("Submitted:", { name, email }); setName(""); setEmail(""); nameRef.current?.focus(); }
return ( <form onSubmit={handleSubmit}> <input ref={nameRef} value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" /> <input ref={emailRef} value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" /> <button type="submit">Create Account</button> {error && <p>{error}</p>} </form> );}useMemo caches the result of an expensive calculation.
import { useMemo } from "react";
const visibleUsers = useMemo(() => { return users.filter((u) => u.active).sort((a, b) => a.name.localeCompare(b.name));}, [users]);useCallback caches a function reference.
import { useCallback } from "react";
const handleClick = useCallback(() => { console.log("Click");}, []);useReducer is useful when state logic is complex or has many related updates.
const [state, dispatch] = useReducer(reducer, initialState);import { useReducer } from "react";
type State = { count: number };type Action = { type: "increment" } | { type: "decrement" } | { type: "reset" };
function reducer(state: State, action: Action): State { switch (action.type) { case "increment": return { count: state.count + 1 }; case "decrement": return { count: state.count - 1 }; case "reset": return { count: 0 }; default: return state; }}
export default function CounterReducer() { const [state, dispatch] = useReducer(reducer, { count: 0 });
return ( <div> <p>{state.count}</p> <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "decrement" })}>-</button> <button onClick={() => dispatch({ type: "reset" })}>Reset</button> </div> );}useLayoutEffect runs after DOM updates but before the browser paints.
useEffect there.useEffect -> after paintuseLayoutEffect -> before paintA custom hook is a function that uses hooks and reuses logic across components.
Name must start with use.
import { useState } from "react";
function useCounter(initial) { const [count, setCount] = useState(initial);
function increment() { setCount((c) => c + 1); }
return { count, increment };}function App() { const { count, increment } = useCounter(0);
return <button onClick={increment}>{count}</button>;}Each component has internal React data (Fiber). Hook values are stored there in order.
Each hook node stores data like:
{ memoizedState, queue, next}Hook Inside Condition
Calling hooks inside if blocks breaks hook order.
Missing Effect Dependency
Missing dependencies can cause stale values and confusing bugs.
Missing Cleanup
Timers and listeners must be cleaned up to avoid leaks.
Overusing Memo Hooks
useMemo and useCallback add complexity when no real gain exists.
if (isOpen) { useState(0); // Wrong}const [count, setCount] = useState(0);
if (!isOpen) { return null;}| Problem | Hook |
|---|---|
| Local value that changes UI | useState |
| Complex state transitions | useReducer |
| Side effects / fetch / subscriptions | useEffect |
| DOM access / mutable box | useRef |
| Expensive computed value | useMemo |
| Stable callback function | useCallback |
| Measure layout before paint | useLayoutEffect |
Keep these simple ideas in mind:
Hooks = ordered state slots for a componentReact stores hook values outside your function, then gives them back on next render