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
