HVRDHVRD
ReactJS

React Hooks

Comprehensive guide to React Hooks, covering core concepts, commonly used hooks, custom hooks, rules of hooks, and advanced usage patterns with detailed explanations and code examples.

React Hooks were introduced in React 16.8 to allow developers to use state and lifecycle features inside functional components, eliminating the need for class components in most cases.

Hooks provide a simpler, more elegant way to manage state, side effects, and context in React applications.


Why Hooks?

Before hooks:

  • Class components were required for state and lifecycle methods.
  • Code reuse between components was difficult without patterns like HOCs or render props.
  • Complex logic often led to tangled, hard-to-maintain code.

With hooks:

  • Functional components can now manage state, side effects, refs, context, and more.
  • Logic can be extracted into custom hooks for reuse.
  • Components become more readable and maintainable.

Rules of Hooks

  1. Only Call Hooks at the Top Level

    • Do not call hooks inside loops, conditions, or nested functions.
    • Always call them in the same order during renders.
  2. Only Call Hooks from React Functions

    • Hooks can only be used inside functional components or custom hooks, not regular JavaScript functions.

Built-in React Hooks

React provides a set of built-in hooks, each serving different purposes.


1. useState

useState lets you add state variables to functional components.

import React, { useState } from "react";

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

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

2. useEffect

import React, { useState, useEffect } from "react";

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(interval); // cleanup
  }, []); // empty dependency => runs once

  return <p>Time: {seconds}s</p>;
}

3. useContext

import React, { useContext } from "react";

const ThemeContext = React.createContext("light");

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

4. useRef

import React, { useRef } from "react";

function InputFocus() {
  const inputRef = useRef(null);

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

  return (
    <div>
      <input ref={inputRef} placeholder="Type here..." />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

5. useMemo

import React, { useState, useMemo } from "react";

function ExpensiveComponent({ num }) {
  const factorial = useMemo(() => {
    const computeFactorial = n =>
      n <= 1 ? 1 : n * computeFactorial(n - 1);
    return computeFactorial(num);
  }, [num]);

  return <p>Factorial: {factorial}</p>;
}

6. useCallback

import React, { useState, useCallback } from "react";

function Button({ onClick, children }) {
  console.log("Button rendered");
  return <button onClick={onClick}>{children}</button>;
}

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

  const increment = useCallback(() => setCount(c => c + 1), []);

  return (
    <div>
      <p>Count: {count}</p>
      <Button onClick={increment}>Increment</Button>
    </div>
  );
}

7. useReducer

import React, { useReducer } from "react";

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment": return { count: state.count + 1 };
    case "decrement": return { count: state.count - 1 };
    default: return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </>
  );
}

8. useLayoutEffect

import React, { useLayoutEffect, useRef } from "react";

function Box() {
  const boxRef = useRef(null);

  useLayoutEffect(() => {
    boxRef.current.style.color = "red";
  });

  return <div ref={boxRef}>Hello World</div>;
}

9. useImperativeHandle

import React, { useRef, forwardRef, useImperativeHandle } from "react";

const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus()
  }));

  return <input ref={inputRef} />;
});

function App() {
  const ref = useRef();

  return (
    <>
      <CustomInput ref={ref} />
      <button onClick={() => ref.current.focus()}>Focus</button>
    </>
  );
}

Custom Hooks

import { useState, useEffect } from "react";

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

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

  return width;
}

function App() {
  const width = useWindowWidth();
  return <p>Window width: {width}px</p>;
}