Skip to main content
This page provides complete, working examples of custom React hooks from the 30 Seconds of Code collection. Each example includes the hook implementation and a practical usage demo.

Timing Hooks

useInterval - Declarative Intervals

Wrapping setInterval() in a React hook requires careful handling of closures and cleanup. Here’s a complete implementation:
const useInterval = (callback, delay) => {
  const savedCallback = React.useRef();

  React.useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  React.useEffect(() => {
    const tick = () => {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
};

useTimeout - Declarative Timeouts

Similar to useInterval, but for one-time delayed execution:
const useTimeout = (callback, delay) => {
  const savedCallback = React.useRef();

  React.useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  React.useEffect(() => {
    const tick = () => {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setTimeout(tick, delay);
      return () => clearTimeout(id);
    }
  }, [delay]);
};

// Usage
const OneSecondTimer = props => {
  const [seconds, setSeconds] = React.useState(0);
  
  useTimeout(() => {
    setSeconds(seconds + 1);
  }, 1000);

  return <p>{seconds}</p>;
};
The implementation is nearly identical to useInterval, with the key difference being setTimeout vs setInterval.

useEffectOnce - Conditional One-Time Effects

Run a callback at most once when a condition becomes true:
const useEffectOnce = (callback, when) => {
  const hasRunOnce = React.useRef(false);

  React.useEffect(() => {
    if (when && !hasRunOnce.current) {
      callback();
      hasRunOnce.current = true;
    }
  }, [when]);
};

// Usage
const App = () => {
  const [clicked, setClicked] = React.useState(false);
  
  useEffectOnce(() => {
    console.log('mounted');
  }, clicked);
  
  return <button onClick={() => setClicked(true)}>Click me</button>;
};

Network Hooks

useFetch - Declarative Data Fetching

Fetch data with proper state management and abort controller support:
const useFetch = (url, options) => {
  const [response, setResponse] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [abort, setAbort] = React.useState(() => {});

  React.useEffect(() => {
    const fetchData = async () => {
      try {
        const abortController = new AbortController();
        const signal = abortController.signal;
        setAbort(abortController.abort);
        const res = await fetch(url, {...options, signal});
        const json = await res.json();
        setResponse(json);
      } catch (error) {
        setError(error);
      }
    };
    fetchData();
    return () => {
      abort();
    }
  }, []);

  return { response, error, abort };
};

Event Hooks

useClickOutside - Outside Click Detection

Detect clicks outside an element - essential for dropdowns, modals, and popovers:
const useClickOutside = (ref, callback) => {
  const handleClick = e => {
    if (ref.current && !ref.current.contains(e.target)) {
      callback();
    }
  };
  
  React.useEffect(() => {
    document.addEventListener('click', handleClick);
    return () => {
      document.removeEventListener('click', handleClick);
    };
  });
};

// Usage
const ClickBox = ({ onClickOutside }) => {
  const clickRef = React.useRef();
  useClickOutside(clickRef, onClickOutside);

  return (
    <div
      className="click-box"
      ref={clickRef}
      style={{
        border: '2px dashed orangered',
        height: 200,
        width: 400,
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center'
      }}
    >
      <p>Click out of this element</p>
    </div>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <ClickBox onClickOutside={() => alert('click outside')} />
);

useClickInside - Inside Click Detection

Complement to useClickOutside for detecting clicks within an element:
const useClickInside = (ref, callback) => {
  const handleClick = e => {
    if (ref.current && ref.current.contains(e.target)) {
      callback();
    }
  };
  
  React.useEffect(() => {
    document.addEventListener('click', handleClick);
    return () => {
      document.removeEventListener('click', handleClick);
    };
  });
};

// Usage
const ClickBox = ({ onClickInside }) => {
  const clickRef = React.useRef();
  useClickInside(clickRef, onClickInside);
  
  return (
    <div
      className="click-box"
      ref={clickRef}
      style={{
        border: '2px dashed orangered',
        height: 200,
        width: 400,
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center'
      }}
    >
      <p>Click inside this element</p>
    </div>
  );
};
Use useClickInside when you need to handle clicks on multiple children without adding individual onClick handlers to each.

Observer Hooks

useMutationObserver - DOM Change Detection

Watch for changes in the DOM tree using the MutationObserver API:
const useMutationObserver = (
  ref,
  callback,
  options = {
    attributes: true,
    characterData: true,
    childList: true,
    subtree: true,
  }
) => {
  React.useEffect(() => {
    if (ref.current) {
      const observer = new MutationObserver(callback);
      observer.observe(ref.current, options);
      return () => observer.disconnect();
    }
  }, [callback, options]);
};

// Usage
const App = () => {
  const mutationRef = React.useRef();
  const [mutationCount, setMutationCount] = React.useState(0);
  
  const incrementMutationCount = () => {
    return setMutationCount(mutationCount + 1);
  };
  
  useMutationObserver(mutationRef, incrementMutationCount);
  const [content, setContent] = React.useState('Hello world');

  return (
    <>
      <label htmlFor="content-input">Edit this to update the text:</label>
      <textarea
        id="content-input"
        style={{ width: '100%' }}
        value={content}
        onChange={e => setContent(e.target.value)}
      />
      <div style={{ width: '100%' }} ref={mutationRef}>
        <div
          style={{
            resize: 'both',
            overflow: 'auto',
            maxWidth: '100%',
            border: '1px solid black',
          }}
        >
          <h2>Resize or change the content:</h2>
          <p>{content}</p>
        </div>
      </div>
      <div>
        <h3>Mutation count {mutationCount}</h3>
      </div>
    </>
  );
};

Lifecycle Hooks

Replicate Class Component Lifecycle

If you’re transitioning from class components, these hooks replicate lifecycle methods:
const useComponentDidMount = onMountHandler => {
  React.useEffect(() => {
    onMountHandler();
  }, []);
};

// Usage
const Mounter = () => {
  useComponentDidMount(() => console.log('Component did mount'));
  return <div>Check the console!</div>;
};

Key Patterns

useRef for Mutable Values

Use useRef when you need values that persist across renders but don’t trigger re-renders when changed.

Cleanup Functions

Always return cleanup functions from effects that add event listeners or create subscriptions.

Dependency Arrays

Include all values from component scope that are used in the effect to avoid stale closures.

Abort Controllers

Use AbortController for network requests to prevent memory leaks and race conditions.

Next Steps

1

Learn Components

See how these hooks are used in React Components.
2

Testing Hooks

Learn how to test custom hooks in the Testing Guide.

Build docs developers (and LLMs) love