[{"data":1,"prerenderedAt":118},["ShallowReactive",2],{"qa-\u002Freact\u002Fhooks\u002Fcustom-hooks":3},{"page":4,"siblings":90,"blog":115},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":81,"related":82,"seo":83,"seoDescription":84,"stem":85,"subtopic":6,"topic":86,"topicSlug":87,"updated":88,"__hash__":89},"qa\u002Freact\u002Fhooks\u002Fcustom-hooks.md","Custom Hooks",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","React","react",{},true,7,"\u002Freact\u002Fhooks\u002Fcustom-hooks",[23,28,32,36,40,44,48,52,56,60,64,68,72,76],{"id":24,"difficulty":25,"q":26,"a":27},"what-is-custom-hook","easy","What is a custom hook?","A custom hook is a **plain JavaScript function** whose name starts with `use` and\nthat calls one or more React hooks internally. It's the primary way to extract and\nshare **stateful logic** between components without changing their structure.\n\n```jsx\nfunction useWindowWidth() {\n  const [width, setWidth] = useState(window.innerWidth)\n  useEffect(() => {\n    const handleResize = () => setWidth(window.innerWidth)\n    window.addEventListener('resize', handleResize)\n    return () => window.removeEventListener('resize', handleResize)\n  }, [])\n  return width\n}\n\nfunction Banner() {\n  const width = useWindowWidth() \u002F\u002F any component can share this logic\n  return \u003Cdiv>{width > 768 ? 'desktop' : 'mobile'}\u003C\u002Fdiv>\n}\n```\n\nEach component that calls a custom hook gets its **own isolated state** — hooks\nare not singletons. Calling `useWindowWidth` in two components gives each its own\n`width` and `handleResize`.\n",{"id":29,"difficulty":25,"q":30,"a":31},"naming-convention","Why must custom hooks start with \"use\"?","React's rules-of-hooks linter (`eslint-plugin-react-hooks`) and React itself use\nthe `use` prefix as the signal that a function *is* a hook and must follow the\nrules of hooks (called at the top level, not in conditions, etc.). Without `use`,\nthe lint rule won't check the function's internals for hook violations.\n\n```jsx\n\u002F\u002F not recognized as a hook — lint won't guard it\nfunction getUser() {\n  const [user] = useState(null) \u002F\u002F lint rule misses this\n  return user\n}\n\n\u002F\u002F recognized, fully linted\nfunction useUser() {\n  const [user] = useState(null)\n  return user\n}\n```\n\nThe `use` prefix is also a convention that communicates to other developers:\n\"this function uses hooks and must follow the rules.\"\n",{"id":33,"difficulty":14,"q":34,"a":35},"custom-hook-vs-utility","What is the difference between a custom hook and a utility function?","A **utility function** is a plain function with no hooks — it can be called\nanywhere (inside or outside React). A **custom hook** calls at least one built-in\nor custom React hook, so it must follow the rules of hooks.\n\n```jsx\n\u002F\u002F utility: no hooks, usable anywhere\nfunction formatDate(date) {\n  return new Intl.DateTimeFormat('en-US').format(date)\n}\n\n\u002F\u002F custom hook: has state, must be called at the top level of a component\nfunction useFormattedDate(date) {\n  const { locale } = useContext(LocaleContext) \u002F\u002F uses a hook\n  return new Intl.DateTimeFormat(locale).format(date)\n}\n```\n\nIf you don't need React hooks, write a plain utility. Extract to a custom hook\nonly when the logic requires state, effects, context, or other hook functionality.\n",{"id":37,"difficulty":25,"q":38,"a":39},"sharing-logic","How do custom hooks compare to HOCs and render props for sharing logic?","All three share stateful logic across components, but hooks are simpler:\n\n- **HOC** wraps a component, adds indirection, and can cause \"wrapper hell\" in\n  DevTools.\n- **Render props** avoid the wrapper issue but require restructuring JSX with a\n  callback function.\n- **Custom hook** is a plain function call — no extra component, no JSX change,\n  full TypeScript inference.\n\n```jsx\n\u002F\u002F HOC\nconst EnhancedComponent = withWindowWidth(MyComponent)\n\n\u002F\u002F render prop\n\u003CWindowWidth>{width => \u003CMyComponent width={width} \u002F>}\u003C\u002FWindowWidth>\n\n\u002F\u002F custom hook — simplest\nconst width = useWindowWidth()\n```\n\nCustom hooks are the idiomatic modern alternative and the reason HOCs\u002Frender\nprops have faded in React codebases.\n",{"id":41,"difficulty":14,"q":42,"a":43},"use-previous","How do you build a usePrevious hook?","Store the previous value in a ref, syncing it to the latest value after each\nrender via an effect.\n\n```jsx\nfunction usePrevious(value) {\n  const ref = useRef()\n  useEffect(() => {\n    ref.current = value \u002F\u002F run after render, so ref still holds old value\n  })\n  return ref.current\n}\n\nfunction Counter() {\n  const [count, setCount] = useState(0)\n  const prevCount = usePrevious(count)\n  return \u003Cp>now: {count}, before: {prevCount}\u003C\u002Fp>\n}\n```\n\nThe key insight: `useEffect` (no deps) runs after every render. So during the\ncurrent render, `ref.current` still has the value from the **previous** render —\nexactly what the caller wants. The update in the effect fires after the return.\n",{"id":45,"difficulty":14,"q":46,"a":47},"use-debounce","How do you build a useDebounce hook?","Maintain a debounced value in state; update it via a timeout that gets reset\nwhenever the raw value changes.\n\n```jsx\nfunction useDebounce(value, delay = 300) {\n  const [debounced, setDebounced] = useState(value)\n  useEffect(() => {\n    const id = setTimeout(() => setDebounced(value), delay)\n    return () => clearTimeout(id) \u002F\u002F reset on new value\n  }, [value, delay])\n  return debounced\n}\n\nfunction Search() {\n  const [query, setQuery] = useState('')\n  const debouncedQuery = useDebounce(query, 400)\n  \u002F\u002F run API call on debouncedQuery, not query\n}\n```\n\nThe cleanup function cancels the pending timeout on every keystroke, so\n`setDebounced` only fires after the user pauses for `delay` ms.\n",{"id":49,"difficulty":14,"q":50,"a":51},"use-localstorage","How do you build a useLocalStorage hook?","Initialize state lazily from localStorage, then sync every update back to\nlocalStorage in an effect.\n\n```jsx\nfunction useLocalStorage(key, initial) {\n  const [value, setValue] = useState(() => {\n    try {\n      const stored = localStorage.getItem(key)\n      return stored !== null ? JSON.parse(stored) : initial\n    } catch {\n      return initial\n    }\n  })\n  useEffect(() => {\n    localStorage.setItem(key, JSON.stringify(value))\n  }, [key, value])\n  return [value, setValue]\n}\n\nconst [theme, setTheme] = useLocalStorage('theme', 'dark')\n```\n\nThe `try\u002Fcatch` handles JSON parse errors or SSR environments where\n`localStorage` doesn't exist. The lazy initializer reads storage once on mount.\n",{"id":53,"difficulty":14,"q":54,"a":55},"use-fetch","How do you build a basic useFetch hook?","Encapsulate the loading\u002Ferror\u002Fdata state and the race-condition guard inside a\nhook that any component can call with a URL.\n\n```jsx\nfunction useFetch(url) {\n  const [state, setState] = useState({ data: null, loading: true, error: null })\n  useEffect(() => {\n    let active = true\n    setState({ data: null, loading: true, error: null })\n    fetch(url)\n      .then(r => r.json())\n      .then(data => { if (active) setState({ data, loading: false, error: null }) })\n      .catch(err => { if (active) setState({ data: null, loading: false, error: err }) })\n    return () => { active = false }\n  }, [url])\n  return state\n}\n```\n\nIn production, prefer a data library (React Query, SWR) that handles caching,\ndeduplication, and retries — but this pattern shows you know the pitfalls.\n",{"id":57,"difficulty":14,"q":58,"a":59},"use-media-query","How do you build a useMediaQuery hook?","Use `window.matchMedia` to evaluate the query and subscribe to changes.\n\n```jsx\nfunction useMediaQuery(query) {\n  const [matches, setMatches] = useState(\n    () => window.matchMedia(query).matches\n  )\n  useEffect(() => {\n    const mq = window.matchMedia(query)\n    const handler = e => setMatches(e.matches)\n    mq.addEventListener('change', handler)\n    return () => mq.removeEventListener('change', handler)\n  }, [query])\n  return matches\n}\n\nconst isDesktop = useMediaQuery('(min-width: 1024px)')\n```\n\nGuard the initial state for SSR (where `window` doesn't exist) by checking\n`typeof window !== 'undefined'` or defaulting to `false`.\n",{"id":61,"difficulty":14,"q":62,"a":63},"composing-hooks","Can custom hooks call other custom hooks?","Yes — and this is encouraged. Composing simple custom hooks into more complex ones\nmirrors how components compose, following the same mental model.\n\n```jsx\nfunction useUserPreferences() {\n  const [theme, setTheme] = useLocalStorage('theme', 'dark') \u002F\u002F custom hook\n  const isDesktop = useMediaQuery('(min-width: 1024px)')     \u002F\u002F custom hook\n  const { locale } = useContext(LocaleContext)\n  return { theme, setTheme, isDesktop, locale }\n}\n```\n\nEach layer stays focused and testable. The top-level hook just orchestrates the\npieces rather than reimplementing each concern.\n",{"id":65,"difficulty":14,"q":66,"a":67},"when-to-extract","When should you extract logic into a custom hook?","Extract when: (1) the same hook combination appears in **two or more** components,\n(2) a component's hook logic is complex enough to obscure what the component\n**renders**, or (3) you want to test the logic independently of the rendering.\n\n```jsx\n\u002F\u002F if this pattern appears in three components, extract it\nconst [data, setData]     = useState(null)\nconst [loading, setLoading] = useState(false)\nconst [error, setError]   = useState(null)\n\u002F\u002F -> extract to useFetch(url)\n```\n\nDon't extract proactively. A hook with a single caller adds indirection without\nbenefit. Wait until duplication or complexity makes the extraction clearly worth\nthe added abstraction.\n",{"id":69,"difficulty":14,"q":70,"a":71},"testing-custom-hook","How do you test a custom hook?","Use `@testing-library\u002Freact`'s `renderHook` utility, which mounts a minimal\ncomponent that calls your hook and returns its result.\n\n```jsx\nimport { renderHook, act } from '@testing-library\u002Freact'\nimport { useCounter } from '.\u002FuseCounter'\n\ntest('increments count', () => {\n  const { result } = renderHook(() => useCounter(0))\n  expect(result.current.count).toBe(0)\n  act(() => result.current.increment())\n  expect(result.current.count).toBe(1)\n})\n```\n\n`act` wraps state updates so the hook's state flushes before your assertions.\nWrap effects or async updates in `act` with `await` if they're async.\n",{"id":73,"difficulty":25,"q":74,"a":75},"hook-return-shape","What can a custom hook return?","Anything — a single value, a tuple (like `useState`), an object, or nothing. The\nconvention is:\n\n- **Tuple** when the hook is analogous to `useState` and the caller will rename\n  the parts: `const [value, setValue] = useLocalStorage(key, init)`.\n- **Object** when there are many named exports and destructuring by name is\n  clearer: `const { data, loading, error } = useFetch(url)`.\n- **Single value** when there's only one thing to return: `const width = useWindowWidth()`.\n\n```jsx\n\u002F\u002F tuple: callers rename freely\nconst [open, setOpen] = useToggle(false)\n\n\u002F\u002F object: callers pick what they need\nconst { user, logout } = useAuth()\n```\n\nChoose based on readability at the call site, not based on what's \"correct.\"\n",{"id":77,"difficulty":78,"q":79,"a":80},"stale-closure-custom-hook","hard","How do stale closure bugs appear in custom hooks?","A custom hook captures props or parent state in closures just like inline hook\ncalls do. If a callback inside the hook isn't in the dependency array, it\ncloses over a stale value.\n\n```jsx\nfunction useInterval(callback, delay) {\n  useEffect(() => {\n    const id = setInterval(callback, delay) \u002F\u002F stale: callback changes but interval isn't reset\n    return () => clearInterval(id)\n  }, [delay]) \u002F\u002F missing `callback`\n}\n```\n\nThe fix: include `callback` in the dep array (causes interval teardown\u002Frecreate\non every render if callback isn't stable) or use the `ref` pattern — mirror the\ncallback into a ref each render and call `ref.current` inside the interval.\n\n```jsx\nfunction useInterval(callback, delay) {\n  const cbRef = useRef(callback)\n  useEffect(() => { cbRef.current = callback }) \u002F\u002F always current\n  useEffect(() => {\n    const id = setInterval(() => cbRef.current(), delay)\n    return () => clearInterval(id)\n  }, [delay])\n}\n```\n",14,null,{"description":11},"React custom hooks interview questions — building your own hooks, naming conventions, composing hooks, usePrevious, useDebounce, useLocalStorage, and testing patterns.","react\u002Fhooks\u002Fcustom-hooks","Hooks","hooks","2026-06-23","79ElYQItZoGOQ8LHERFD37rm-3y2afQZJ2HQBfanTkY",[91,95,98,102,106,110,114],{"subtopic":92,"path":93,"order":94},"useState","\u002Freact\u002Fhooks\u002Fusestate",1,{"subtopic":96,"path":97,"order":12},"useEffect","\u002Freact\u002Fhooks\u002Fuseeffect",{"subtopic":99,"path":100,"order":101},"useContext","\u002Freact\u002Fhooks\u002Fusecontext",3,{"subtopic":103,"path":104,"order":105},"useReducer","\u002Freact\u002Fhooks\u002Fusereducer",4,{"subtopic":107,"path":108,"order":109},"useCallback & useMemo","\u002Freact\u002Fhooks\u002Fusecallback-usememo",5,{"subtopic":111,"path":112,"order":113},"useRef","\u002Freact\u002Fhooks\u002Fuseref",6,{"subtopic":6,"path":21,"order":20},{"path":116,"title":117},"\u002Fblog\u002Freact-custom-hooks-guide","React Custom Hooks — Complete Interview Guide with Examples",1782244100859]