Irbisa · Cheatsheet

Effects & Refs

useEffect and its dependencies, cleanup, race conditions, when you do not need an effect, useRef, useLayoutEffect, StrictMode double run

Middle Developer · 19 questions

  1. 01

    What is useEffect for, and when does it run?

    useEffect synchronizes a component with something outside React: a subscription, a timer, a browser API, a network connection.

    function ChatRoom({ roomId }: { roomId: string }) {  useEffect(() => {    const connection = createConnection(roomId);    connection.connect();    return () => connection.disconnect(); // runs before the next effect and on unmount  }, [roomId]); // reconnect only when roomId changes…
  2. 02

    How does the dependency array of useEffect work: no array, an empty array, and a list of values?

    With no array the effect runs after every render; with [] it runs once after mount, plus cleanup on unmount; with [a, b] it runs after mount and after every render in which a or b changed.

    useEffect(() => {  document.title = `${count} new messages`;}); // after every render useEffect(() => {  const id = setInterval(refreshClock, 1000);…
  3. 03

    What is the cleanup function of an effect, and when does React call it?

    The cleanup is the function you return from the effect; it undoes what the effect set up: unsubscribes, clears a timer, aborts a request.

    function useWindowWidth() {  const [width, setWidth] = useState(window.innerWidth);   useEffect(() => {    const onResize = () => setWidth(window.innerWidth);    window.addEventListener('resize', onResize);…
  4. 04

    What is useRef, and how is it different from state?

    useRef(initial) returns a mutable object { current } that lives as long as the component and keeps the same identity across renders.

    function Stopwatch() {  const [elapsed, setElapsed] = useState(0); // shown on screen: state  const intervalRef = useRef<number | null>(null); // not shown: ref   function start() {    if (intervalRef.current !== null) return;…
  5. 05

    How do you access a DOM element through a ref, and how do you pass a ref to your own component in React 19?

    Create a ref with useRef<HTMLInputElement>(null) and pass it to the element's ref prop; after commit ref.current holds the DOM node, and on unmount it goes back to null.

    function TextInput({ ref, ...props }: React.ComponentProps<'input'>) {  return <input ref={ref} className="input" {...props} />;} function SearchForm() {  const inputRef = useRef<HTMLInputElement>(null);…
  6. 06

    Why do effects run twice in development, and how should you deal with it?

    In development <StrictMode> mounts every component, unmounts it and mounts it again, so each effect runs setup, cleanup, setup.

  7. 07

    What is a race condition when fetching data in useEffect, and how do you prevent it?

    When a dependency changes quickly (typing in a search box, switching tabs), several requests are in flight, and a slower old response can arrive last and overwrite the new one.

    type Hit = { id: string; title: string }; function SearchResults({ query }: { query: string }) {  const [results, setResults] = useState<Hit[]>([]);   useEffect(() => {…
  8. 08

    When do you not need an effect, and what do you use instead?

    You do not need an effect when nothing outside React is involved: data you can compute from props or state is calculated during render, and work caused by a user action goes into the event handler.

  9. 09

    Find the bug: this effect runs in an endless loop and keeps sending requests.

    function Products({ category }: { category: string }) {  const [products, setProducts] = useState<Product[]>([]);  const filters = { category, inStock: true };   useEffect(() => {    fetchProducts(filters).then(setProducts);  }, [filters]);   return <ProductGrid items={products} />;}

    filters is a new object on every render, so the dependency always differs by Object.is: the effect fetches, setProducts stores a new array, the component re-renders, creates another filters, and the cycle repeats.

  10. 10

    Find the bug: React logs "useEffect must not return anything besides a function", and the page goes blank when userId changes.

    function Profile({ userId }) {  const [user, setUser] = useState(null);   useEffect(async () => {    const response = await fetch(`/api/users/${userId}`);    setUser(await response.json());  }, [userId]);   return <h1>{user ? user.name : 'Loading...'}</h1>;}

    The effect callback is async, so it returns a promise, and React treats whatever an effect returns as its cleanup.

  11. 11

    What does this component log on mount and after one click (without StrictMode)?

    function Logger() {  const [count, setCount] = useState(0);  console.log('render', count);   useEffect(() => {    console.log('effect', count);    return () => console.log('cleanup', count);  }, [count]);   return <button onClick={() => setCount(count + 1)}>{count}</button>;}

    On mount: render 0, effect 0.

  12. 12

    How do you implement a usePrevious hook, and what are its limits?

    The classic version stores the value in a ref inside an effect, so during a render the ref still holds the value from the previous commit.

    // classic: the value from the previous renderfunction usePrevious<T>(value: T): T | undefined {  const ref = useRef<T | undefined>(undefined);  useEffect(() => {    ref.current = value;  });…
  13. 13

    Find the bug: the counter goes from 0 to 1 and then stops increasing.

    function Ticker() {  const [count, setCount] = useState(0);   useEffect(() => {    const id = setInterval(() => {      setCount(count + 1);    }, 1000);    return () => clearInterval(id);  }, []);   return <p>{count}</p>;}

    The interval callback was created in the first render and closes over count = 0, so every tick calls setCount(0 + 1); after the first tick the state is already 1 and nothing changes.

  14. 14

    What is the difference between useEffect and useLayoutEffect, and when do you need the latter?

    useLayoutEffect runs synchronously after React updates the DOM but before the browser paints; useEffect usually runs after paint.

    function Tooltip({ anchor, text }: { anchor: DOMRect; text: string }) {  const ref = useRef<HTMLDivElement>(null);  const [top, setTop] = useState(anchor.bottom);   useLayoutEffect(() => {    if (!ref.current) return;…
  15. 15

    What is useImperativeHandle, and when would you expose methods through a ref?

    useImperativeHandle(ref, () => handle) sets what a parent's ref receives from your component: a small object of methods instead of the raw DOM node.

    type VideoHandle = { play: () => void; pause: () => void }; function Video({ src, ref }: { src: string; ref?: React.Ref<VideoHandle> }) {  const videoRef = useRef<HTMLVideoElement>(null);   useImperativeHandle(ref, () => ({…
  16. 16

    How do you close a dropdown when the user clicks outside it?

    While the dropdown is open, listen for pointerdown on document and close it when the event target is not inside the dropdown (!ref.current.contains(e.target)).

    function Dropdown({ items }: { items: string[] }) {  const [open, setOpen] = useState(false);  const rootRef = useRef<HTMLDivElement>(null);   useEffect(() => {    if (!open) return;…
  17. 17

    How do you use a library that owns its own DOM, such as a map or a chart, inside a React component?

    Render an empty container with a ref, create the library instance in an effect, keep it in a ref and destroy it in the cleanup.

    import Chart from 'chart.js/auto'; function SalesChart({ labels, values }: { labels: string[]; values: number[] }) {  const canvasRef = useRef<HTMLCanvasElement>(null);  const chartRef = useRef<Chart<'line', number[], string> | null>(null);…
  18. 18

    How do you read the latest props or state inside an effect without re-running it, and what is useEffectEvent?

    Move the non-reactive part into an effect event: const onConnected = useEffectEvent(() => showNotification(theme)).

    import { useEffect, useEffectEvent } from 'react'; function ChatRoom({ roomId, theme }: { roomId: string; theme: 'light' | 'dark' }) {  const onConnected = useEffectEvent(() => {    showNotification('Connected!', theme); // reads the latest theme  });…
  19. 19

    What are callback refs, and what changed about them in React 19?

    A callback ref is a function passed to ref: React calls it with the DOM node when the node is attached and, before React 19, with null when it is detached.

    function MeasuredBox({ children }: { children: React.ReactNode }) {  const [height, setHeight] = useState(0);   const measure = useCallback((node: HTMLDivElement) => {    const observer = new ResizeObserver(([entry]) => setHeight(entry.contentRect.height));    observer.observe(node);…