What Are React Hooks? Explain useState and useEffect

Beginner10 min interview
Skills tested:
Explaining what hooks are and the two rules that govern where and how they can be calledUsing useState to manage local component state and explaining the setter trigger re-render cycleUsing useEffect to handle side effects and explaining its three dependency array modesMapping class lifecycle methods to their hook equivalentsKnowing that hooks are composable: custom hooks are functions that call other hooks

Advertisement

🧩 Scenario

React Hooks, introduced in React 16.8, are the foundation of modern React development. Every interview question about state, effects, context, refs, memoization, and custom logic in React components ultimately involves hooks. A solid explanation of useState and useEffect, including the rules and the lifecycle equivalences, is the expected baseline for any React role.

Architecture Walkthrough

What Hooks Are

Hooks are functions whose names start with use that let functional components access React features: state, lifecycle events, context, refs, and memoization. Before hooks (React 16.7 and earlier), these features were only available in class components. Hooks made all of them available in functional components without changing the component model.

Two rules govern all hooks. First, hooks must be called at the top level of a component function, not inside conditions, loops, or nested functions. Second, hooks must be called from functional components or other hooks, not from regular JavaScript functions. These rules exist because React identifies each hook call by its order of invocation in the component. If hooks are called conditionally, the order changes between renders and React's state tracking breaks.

useState: State in Function Components

useState accepts an initial value and returns a pair: the current state value and a setter function. Calling the setter with a new value schedules a re-render. On the next render, the state variable holds the new value. The setter can accept a value directly or an updater function that receives the previous state.

Each useState call manages one piece of state. Multiple pieces of state are managed by multiple useState calls. React tracks them by call order, which is why the rules-of-hooks exist.

useEffect: Side Effects in Function Components

useEffect accepts a callback that runs after render. The optional second argument (the dependency array) controls when the callback re-runs. With no array, it runs after every render. With an empty array, it runs only after the first render. With a non-empty array, it runs after the first render and after any render where one of the listed values changed. The callback can return a cleanup function that runs before the next run of the effect or when the component unmounts.

useEffect replaces componentDidMount (empty array), componentDidUpdate (non-empty array), and componentWillUnmount (the cleanup return).


Key Code Explained

// useState: local state with re-render trigger
function Counter() {
  const [count, setCount] = useState(0); // initial value: 0

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

// Multiple state variables
function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsSubmitting(true);
    await login(email, password);
    setIsSubmitting(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={(e) => setEmail(e.target.value)} type="email" />
      <input value={password} onChange={(e) => setPassword(e.target.value)} type="password" />
      <button disabled={isSubmitting}>{isSubmitting ? 'Signing in...' : 'Sign In'}</button>
    </form>
  );
}


// useEffect: the three dependency array modes
function DataLoader({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);

  // Mount only: empty array
  useEffect(() => {
    setupAnalytics();
    return () => teardownAnalytics();
  }, []); // componentDidMount + componentWillUnmount

  // Specific dep: runs on mount and when userId changes
  useEffect(() => {
    let cancelled = false;
    fetchUser(userId).then((data) => {
      if (!cancelled) setUser(data);
    });
    return () => { cancelled = true; }; // componentWillUnmount / before next run
  }, [userId]); // componentDidUpdate for userId + initial mount

  if (!user) return <Skeleton />;
  return <UserCard user={user} />;
}


// Custom hook: extract and reuse stateful logic
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize, { passive: true });
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return width;
}

// Usage in any component
function ResponsiveLayout() {
  const width = useWindowWidth(); // encapsulated state + effect
  return <div>{width > 768 ? <DesktopLayout /> : <MobileLayout />}</div>;
}

The custom hook useWindowWidth demonstrates the main advantage of hooks for code reuse. The same logic (adding a resize listener, tracking window width, removing the listener) can be shared between any number of components by calling the hook. Before hooks, this required Higher-Order Components or render props, both of which require restructuring the component tree.


Tradeoffs

Class componentHook equivalentNotes
this.state + setStateuseStateMultiple calls for multiple state slices
componentDidMountuseEffect(() => {}, [])Empty dep array
componentDidUpdateuseEffect(() => {}, [dep])Specific dep array
componentWillUnmountuseEffect returnCleanup function returned from effect
HOC / render propsCustom hookLogic reuse without wrapping the tree

What Interviewers Actually Check

  • Whether you can state the two rules of hooks and explain why they exist
  • Whether you can explain the useState trigger-re-render cycle
  • Whether you can describe all three useEffect modes (no array, empty array, specific deps)
  • Whether you can write a cleanup function and explain when it fires
  • Whether you can describe custom hooks and why they are the hook-era replacement for HOCs

Follow-Up Questions

  1. What are the other built-in hooks besides useState and useEffect, and when would you reach for useReducer, useContext, useRef, or useMemo?
  2. How does React know which state belongs to which useState call if they have no identifiers?
  3. What is the useOptimistic hook introduced in React 19 and how does it change the mutation flow?
  4. How would you write a useDebounce custom hook that delays a value update?
  5. What is the purpose of React.StrictMode and how does it affect hook behavior in development?

Common Candidate Mistakes

  • Calling useState inside an if statement and not knowing why it breaks React's state tracking
  • Not knowing that the state setter triggers a re-render and that the new value is only available on the next render
  • Describing useEffect as only running "once on mount" when that is only the empty-array mode
  • Not knowing that a custom hook is just a function that calls other hooks, not a special React construct
  • Claiming hooks are available in class components

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you state the two rules of hooks and explain why they exist?
  • Can you write a useState example showing the setter, re-render, and accessing the new value?
  • Can you write a useEffect in each of the three modes: every render, mount only, specific dep?
  • Can you write a custom hook and explain how it enables logic reuse?
  • Can you map componentDidMount, componentDidUpdate, and componentWillUnmount to useEffect?

Summary

Hooks are functions prefixed with use that give functional components access to React features that previously required class components. The two rules that govern them (top-level only, functional components only) exist because React tracks hook state by call order; conditional or nested calls would change the order across renders and corrupt React's state tracking.

useState manages a single piece of local state. Calling the setter schedules a re-render; the new value is available in the next render. useEffect runs side effects after renders. Its behavior depends on the dependency array: no array runs after every render, empty array runs after the first render only, a non-empty array runs after mount and whenever any listed value changes. The optional cleanup function returned from the effect fires before the next run and on unmount.

Custom hooks are the key code-reuse pattern of the hooks era. A custom hook is a regular function that calls other hooks. It extracts stateful logic from a component, making it reusable across multiple components without wrapping the component tree in Higher-Order Components or passing render props.

Frequently Asked Questions

Can I use hooks inside class components?

No. Hooks are only valid inside functional components and other hooks. Class components use this.state and lifecycle methods instead.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement