React

React Hooks — useState, useEffect, useContext & More

Thirdy Gayares
20 min read

🎓 What You Will Learn

  • useState: Managing component state with multiple values
  • useEffect: Side effects and cleanup with dependencies
  • useReducer: Complex state logic with actions
  • useContext: Avoiding prop drilling with global state
  • useCallback: Memoize callback functions for performance
  • useMemo: Memoize expensive computations
  • Custom Hooks: Create reusable hook logic
  • Hook Rules: When and where to use hooks correctly
1

What Are React Hooks?

Hooks are special functions that let you use React features in functional components. They "hook into" React state and lifecycle features without writing class components.

Before hooks, you had to use class components to access state and lifecycle. Hooks changed everything by making these features available in functional components.

2

useState — Multiple State Values

useState lets you add state to functional components. You can use it multiple times to manage different pieces of state.

useState.jsx
import { useState } from 'react';

export function UserForm() {
  // Each useState call creates separate state
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [age, setAge] = useState("");

  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <input
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="number"
        value={age}
        onChange={(e) => setAge(e.target.value)}
        placeholder="Age"
      />
    </div>
  );
}

Name: Alice

Age: 25

Active: ✅ Yes

3

useEffect — Side Effects & Cleanup

useEffect lets you perform side effects in components. Side effects include fetching data, updating document title, setting up subscriptions, etc.

1useEffect Basics
useEffect-basics.jsx
useEffect(() => {
  // This runs after every render
  console.log("Component rendered!");
});

useEffect(() => {
  // This runs only once (on mount)
  console.log("Component mounted!");
}, []);

useEffect(() => {
  // This runs when count changes
  console.log("Count changed:", count);
}, [count]);
2Cleanup Function
useEffect-cleanup.jsx
useEffect(() => {
  // Setup
  const timer = setInterval(() => {
    console.log("Timer running");
  }, 1000);

  // Cleanup function
  return () => {
    clearInterval(timer);
    console.log("Cleanup!");
  };
}, []);

Count: 0

👆 Check browser tab title - it updates with useEffect!

4

useReducer — Complex State Logic

useReducer is for managing complex state with multiple related values. It's like useState but with more control.

reducer-pattern.jsx
const initialState = { count: 0, step: 1 };

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + state.step };
    case 'SET_STEP':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

// Usage
const [state, dispatch] = useReducer(reducer, initialState);

<button onClick={() => dispatch({ type: 'INCREMENT' })}>
  Increment by {state.step}
</button>
When to use useReducer: Multiple related state values, complex state transitions, or passing dispatch to multiple components.

Count: 0

Step: 1

5

useContext — Avoiding Prop Drilling

useContext lets you access global state without passing props down through every component (prop drilling).

useContext-pattern.jsx
import { createContext, useContext } from 'react';

// 1. Create context
const ThemeContext = createContext();

// 2. Create provider
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. Use context in any component
function Button() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button onClick={() => setTheme(...)}>
      Current theme: {theme}
    </button>
  );
}
⚠️ Context Performance: When context value changes, all consuming components rerender. For large apps, consider additional optimization or state management libraries.

Current Theme: ☀️ Light

6

useCallback — Memoize Callbacks

useCallback memoizes a function. It's useful when passing callbacks to optimized child components that rely on reference equality.

useCallback.jsx
import { useCallback, useState } from 'react';

export function Parent() {
  const [count, setCount] = useState(0);

  // handleClick will be same reference unless count changes
  const handleClick = useCallback(() => {
    console.log("Clicked! Count is:", count);
  }, [count]); // Re-create only when count changes

  return <Child onClick={handleClick} />;
}

Count: 0

Result: 0

Component Renders: 0

7

useMemo — Memoize Values

useMemo memoizes a computed value. Use it for expensive calculations that shouldn't rerun on every render.

useMemo.jsx
import { useMemo, useState } from 'react';

export function ExpensiveComponent() {
  const [count, setCount] = useState(0);

  // This calculation only runs when count changes
  const expensiveValue = useMemo(() => {
    console.log("Calculating...");
    return count * count * count; // Expensive!
  }, [count]);

  return <p>Result: {expensiveValue}</p>;
}

Count: 0

Result: 0

✨ Calculation memoized - only recalculates when count changes

8

Custom Hooks — Reusable Logic

Custom hooks are JavaScript functions that use other hooks. They let you extract component logic into reusable functions.

custom-hook-pattern.jsx
// Custom Hook
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : initialValue;
  });

  const setStorageValue = (value) => {
    setValue(value);
    localStorage.setItem(key, JSON.stringify(value));
  };

  return [value, setStorageValue];
}

// Usage in any component
function App() {
  const [name, setName] = useLocalStorage('name', 'Guest');
  return (
    <input
      value={name}
      onChange={(e) => setName(e.target.value)}
    />
  );
}
✅ Custom Hook Rules: Name starts with use, can use other hooks, called at top level only.

Saved Name: Guest

💾 Persisted in localStorage

9

Rules of Hooks (Critical!)

❌ Rule 1: Only call hooks at top level
hook-rule-1.jsx
// WRONG: Conditional hooks
if (isCondition) {
  const [count, setCount] = useState(0); // ❌
}

// RIGHT: Always call at top level
const [count, setCount] = useState(0);
❌ Rule 2: Only call hooks from React functions
hook-rule-2.jsx
// WRONG: Regular JavaScript function
function regularFunction() {
  const [count, setCount] = useState(0); // ❌
}

// RIGHT: Component or custom hook
function MyComponent() {
  const [count, setCount] = useState(0); // ✅
}

function useCustomHook() {
  const [count, setCount] = useState(0); // ✅
}
❌ Rule 3: Dependency array matters
hook-rule-3.jsx
// WRONG: Missing dependency
useEffect(() => {
  console.log(count); // Uses 'count' but not in dependencies
}, []);

// RIGHT: Include all dependencies
useEffect(() => {
  console.log(count);
}, [count]);
10

All Built-in Hooks Reference

HookPurposeExample
useStateAdd stateconst [x, setX] = useState(0)
useEffectSide effectsuseEffect(() => {...}, [deps])
useContextRead contextconst x = useContext(Context)
useReducerComplex stateconst [state, dispatch] = useReducer(...)
useRefDOM referenceconst ref = useRef()
useCallbackMemoize functionconst fn = useCallback(..., [deps])
useMemoMemoize valueconst val = useMemo(..., [deps])
useLayoutEffectBefore paintuseLayoutEffect(() => {...})
11

Best Practices

Use multiple useState calls: One per piece of state (cleaner than one big object)
Always include dependencies: Don't skip the dependency array in useEffect
Extract to custom hooks: Reuse logic across components
Use useReducer for complex state: When you have multiple related values
Don't optimize prematurely: Only use useCallback/useMemo when you see performance issues
12

Common Hook Patterns

1Fetch Data on Mount
fetch-pattern.jsx
useEffect(() => {
  let isMounted = true;

  fetch('/api/data')
    .then(res => res.json())
    .then(data => {
      if (isMounted) {
        setData(data);
      }
    });

  return () => {
    isMounted = false; // Cleanup
  };
}, []); // Run once on mount
2Debounced Search
debounce-pattern.jsx
const [search, setSearch] = useState("");

useEffect(() => {
  const timer = setTimeout(() => {
    // Perform search
    console.log("Searching for:", search);
  }, 500);

  return () => clearTimeout(timer); // Cleanup
}, [search]); // Run when search changes
13

Common Mistakes

❌ Mistake: Missing cleanup
mistake-cleanup.jsx
// WRONG: Memory leak
useEffect(() => {
  window.addEventListener('resize', handleResize);
  // Missing cleanup!
}, []);

// RIGHT: Cleanup listeners
useEffect(() => {
  window.addEventListener('resize', handleResize);
  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []);
❌ Mistake: Stale closures in effects
mistake-closure.jsx
// WRONG: count is always 0
useEffect(() => {
  const timer = setInterval(() => {
    setCount(count + 1); // count is always 0!
  }, 1000);
  return () => clearInterval(timer);
}, []); // Missing count dependency

// RIGHT: Use functional update
useEffect(() => {
  const timer = setInterval(() => {
    setCount(prev => prev + 1);
  }, 1000);
  return () => clearInterval(timer);
}, []);
14

What's Next?

🚀 Advanced Topics:
  1. 1. State Management: Context API patterns, Redux, Zustand
  2. 2. Performance Optimization: Profiling, React.memo, code splitting
  3. 3. API Integration: Fetching, caching, error handling
  4. 4. Testing Hooks: React Testing Library for hooks
  5. 5. Advanced Patterns: Higher-Order Components, Render Props

Master hooks through practice! 🚀 Build real projects using these hooks and you'll become proficient quickly. Start with useState and useEffect, then move to useReducer and custom hooks.

About the Author

TG

Thirdy Gayares

Passionate developer creating custom solutions for everyone. I specialize in building user-friendly tools that solve real-world problems while maintaining the highest standards of security and privacy.