Custom Hooks, Refs and Context
Reuse logic with custom hooks and share data with context.
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>Every consumer re-renders when the context value changes. Keep contexts small and focused, and prefer plain props when data only goes one or two levels down.
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.
Try it yourself
Write a useToggle(initial) custom hook that returns [value, toggle], then use it for a show/hide panel.
Show solution
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue((v) => !v);
return [value, toggle];
}
function Panel() {
const [open, toggle] = useToggle();
return (
<>
<button onClick={toggle}>{open ? "Hide" : "Show"}</button>
{open && <p>Secret panel</p>}
</>
);
}