Learn / Frameworks / React / Custom Hooks, Refs and Context

React · Lesson 6 of 7

Custom Hooks, Refs and Context

Reuse logic with custom hooks and share data with context.

  • Intermediate
  • 17 min read
  • 3 objectives

Before this lessonLesson 5: Effects with useEffect

What you will learn

  • Extract a custom hook
  • Use useRef
  • Share data with context

Hooks are ordinary functions, so you can package repeated logic into your own. This lesson covers three tools that make larger apps manageable: custom hooks, refs and context.

Custom hooks

A custom hook is a function whose name starts with use and which calls other hooks. It shares logic, not state: each component that calls it gets its own copy.

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initial;
  });
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  return [value, setValue];
}

function Settings() {
  const [theme, setTheme] = useLocalStorage("theme", "light");
  return <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>{theme}</button>;
}

Passing a function to useState (lazy initialization) means the expensive read runs only on the first render.

useRef

A ref holds a value that persists across renders but does not cause a re-render when it changes. Two main uses: reaching a DOM element, and storing mutable values like timer ids.

import { useRef } from "react";

function SearchBox() {
  const inputRef = useRef(null);
  return (
    <>
      <input ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </>
  );
}

Context

Passing props through many layers just to reach a deeply nested component is called prop drilling. Context lets a parent make a value available to its entire subtree. Good candidates: the current user, the theme, the language.

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext("light");

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

function ThemeButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  return <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Theme: {theme}</button>;
}

// <ThemeProvider><Page /></ThemeProvider>

useMemo and useCallback

useMemo caches an expensive computed value; useCallback caches a function so its identity stays stable. Do not add them by default; use them when profiling shows a real problem or when a stable identity is needed for a dependency array.

// Write your solution here
Up next · Lesson 7Data Fetching and RoutingLoad data with loading and error states, then add pages with React Router.