Creating Custom Hooks
A custom hook is a JavaScript function whose name starts with use and that may call other hooks.
function useSomething() {
// call hooks here
return something;
}The use prefix matters because React tooling uses it to check hook rules.
Start from a Component
Begin with working component logic.
function SearchPage() {
const [query, setQuery] = useState("");
const [debouncedQuery, setDebouncedQuery] = useState(query);
useEffect(() => {
const id = setTimeout(() => {
setDebouncedQuery(query);
}, 300);
return () => clearTimeout(id);
}, [query]);
return <SearchResults query={debouncedQuery} onQueryChange={setQuery} />;
}The debounce behavior can be named and reused.
Extract the Hook
function useDebouncedValue(value, delayMs) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const id = setTimeout(() => {
setDebouncedValue(value);
}, delayMs);
return () => clearTimeout(id);
}, [value, delayMs]);
return debouncedValue;
}Now the component is smaller and clearer.
function SearchPage() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebouncedValue(query, 300);
return <SearchResults query={debouncedQuery} onQueryChange={setQuery} />;
}Designing the Return Value
Return a single value when the hook has one obvious result.
const isOnline = useOnlineStatus();Return an object when there are named values and actions.
const { value, setValue, reset } = useLocalStorage("theme", "light");Return an array when the API intentionally mirrors a built-in hook.
const [isOpen, toggle] = useToggle(false);Inputs and Dependencies
Custom hook parameters are reactive values too. If an effect inside the hook reads a parameter, it belongs in that effect's dependency array.
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}Common Mistakes
Do not call custom hooks conditionally.
if (enabled) {
useDebouncedValue(query, 300); // wrong
}Put the condition inside the hook or inside an effect within the hook.
Do not return unstable callbacks unless callers do not care about identity. If a hook returns functions used in dependencies or memoized children, consider useCallback.
Why should a custom hook name start with use?
Testing Awareness
Custom hooks are often testable because logic is separated from rendering. You can test them through a small component or hook testing utility. Focus on behavior: initial value, updates, cleanup, and edge cases.
For hooks that touch browser APIs, mock the API or wrap it so tests can control the environment.
Practice Challenge
Create a useToggle hook.
Requirements:
- accept an optional initial boolean
- return the current value
- return
toggle,turnOn, andturnOfffunctions - keep returned callbacks stable with
useCallback - write down two edge cases to test
Recap
Create custom hooks by extracting a named stateful concern from a component. Keep the API small, include dependencies honestly, follow hook rules, and design return values that are easy for components to use.