</>StackKit
</>StackKit

Developer tutorials & guides

React component code
React

React Hooks Explained: A Beginner's Guide with Real Examples

Understand useState, useEffect, useRef, useContext and custom hooks with practical examples that show you exactly when and why to use each one.

P
Priya Nair
April 22, 20259 min read
#react#hooks#javascript#frontend

What Are React Hooks?

Hooks are functions that let you "hook into" React state and lifecycle features from function components. Before hooks (introduced in React 16.8), you needed class components for state and lifecycle logic. Hooks made function components just as powerful — and much cleaner.


useState — Managing Local State

useState gives a component its own memory.

import { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Key rules:

  • useState returns a tuple: [currentValue, setter]
  • Calling the setter triggers a re-render
  • Never mutate state directly — always use the setter

For objects, spread to avoid mutation:

const [user, setUser] = useState({ name: '', email: '' });

setUser(prev => ({ ...prev, name: 'Alex' }));

useEffect — Side Effects & Lifecycle

useEffect runs code after render. It replaces componentDidMount, componentDidUpdate, and componentWillUnmount.

import { useState, useEffect } from 'react';

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]); // re-runs when userId changes

  return <div>{user?.name}</div>;
}

The dependency array:

  • [] — runs once on mount (like componentDidMount)
  • [value] — runs on mount and whenever value changes
  • Omitted — runs after every render (usually a bug)

Cleanup:

useEffect(() => {
  const timer = setInterval(() => tick(), 1000);
  return () => clearInterval(timer); // cleanup on unmount
}, []);

useRef — Accessing DOM & Persisting Values

useRef gives you a mutable container that doesn't trigger re-renders.

DOM access:

function SearchInput() {
  const inputRef = useRef<HTMLInputElement>(null);

  const focus = () => inputRef.current?.focus();

  return (
    <>
      <input ref={inputRef} />
      <button onClick={focus}>Focus</button>
    </>
  );
}

Persisting values across renders (without re-rendering):

const renderCount = useRef(0);
renderCount.current += 1;

useContext — Global State Without Prop Drilling

useContext lets any component read from a context without passing props down manually.

const ThemeContext = createContext<'light' | 'dark'>('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return <ThemedButton />;
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Click</button>;
}

Custom Hooks — Reusable Logic

Any function that starts with use and calls other hooks is a custom hook. They're the best way to share logic across components.

function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setStoredValue = (newValue: T) => {
    setValue(newValue);
    localStorage.setItem(key, JSON.stringify(newValue));
  };

  return [value, setStoredValue] as const;
}

// Usage
const [theme, setTheme] = useLocalStorage('theme', 'light');

Rules of Hooks

  1. Only call hooks at the top level — not inside loops, conditions, or nested functions.
  2. Only call hooks from React functions — function components or custom hooks.

The ESLint plugin eslint-plugin-react-hooks enforces these automatically.


Conclusion

Hooks completely changed how React apps are written. Start with useState and useEffect — they cover 80% of use cases. Add useRef when you need DOM access or stable values, and useContext when prop drilling becomes painful. Once those feel natural, build custom hooks to encapsulate and share your logic cleanly.

#react#hooks#javascript#frontend