Hone logo
Hone
Problems

Custom React Hook: useInterval

Build a custom React hook called useInterval that allows components to execute a callback function at a specified interval. This hook is essential for implementing features like polling, animations, or any recurring task within a React application.

Problem Description

Your task is to create a custom React hook, useInterval, that mimics the functionality of setInterval but integrates seamlessly with React's lifecycle and state management. The hook should accept a callback function and a delay in milliseconds. The callback should be executed repeatedly at the specified interval.

Key Requirements:

  • The hook should accept two arguments:
    • callback: A function to be executed at each interval.
    • delay: A number representing the interval in milliseconds. If null or undefined, the interval should be cleared.
  • The hook should manage the setInterval and clearInterval calls internally.
  • The callback function should be able to access the latest state and props without issues caused by closure. This means that if the state used within the callback changes, the useInterval hook should pick up the latest value on the next execution of the callback.
  • When the delay prop changes, the interval should be reset with the new delay.
  • When the component using the hook unmounts, the interval must be cleared to prevent memory leaks.

Expected Behavior:

  • When delay is a positive number, the callback function should be invoked every delay milliseconds.
  • When delay is set to null or undefined, the interval should be stopped.
  • If the callback function itself is redefined (e.g., due to a parent component re-render that passes a new function), the hook should use the latest version of the callback.

Edge Cases:

  • What happens if delay is 0? (The behavior should be consistent with setInterval where it might execute immediately or with a very small delay, but for this hook, consider it an immediate execution if possible or a minimum practical delay).
  • What happens if the callback function throws an error? (The hook should ideally not crash the application, though error handling within the callback itself is the responsibility of the user).

Examples

Example 1: Basic Interval

import React, { useState } from 'react';
import { useInterval } from './useInterval'; // Assuming your hook is in this file

function Counter() {
  const [count, setCount] = useState(0);

  useInterval(() => {
    // This callback needs to access the latest 'count' state
    setCount(count + 1);
  }, 1000); // Interval of 1 second

  return <div>Count: {count}</div>;
}

// Expected output after 5 seconds:
// Count: 5

Example 2: Clearing the Interval

import React, { useState } from 'react';
import { useInterval } from './useInterval';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(true);

  useInterval(() => {
    setSeconds(prevSeconds => prevSeconds + 1);
  }, isRunning ? 1000 : null); // Stop interval when isRunning is false

  return (
    <div>
      <p>Seconds: {seconds}</p>
      <button onClick={() => setIsRunning(false)}>Stop Timer</button>
      <button onClick={() => setIsRunning(true)}>Start Timer</button>
    </div>
  );
}

// If the user clicks "Stop Timer" when seconds is 5, the output will remain:
// Seconds: 5
// and will not increment further.

Example 3: Changing Delay

import React, { useState } from 'react';
import { useInterval } from './useInterval';

function DynamicInterval() {
  const [value, setValue] = useState(0);
  const [intervalDelay, setIntervalDelay] = useState(1000); // Initially 1 second

  useInterval(() => {
    setValue(prevValue => prevValue + 1);
  }, intervalDelay);

  return (
    <div>
      <p>Value: {value}</p>
      <button onClick={() => setIntervalDelay(500)}>Speed Up (500ms)</button>
      <button onClick={() => setIntervalDelay(2000)}>Slow Down (2000ms)</button>
      <button onClick={() => setIntervalDelay(null)}>Stop</button>
    </div>
  );
}

// If the user clicks "Speed Up (500ms)" when value is 3 and intervalDelay was 1000,
// the interval will immediately start ticking every 500ms.

Constraints

  • The callback function must be a valid JavaScript function.
  • The delay must be a number (milliseconds) or null / undefined.
  • The useInterval hook must be implemented using React Hooks (useState, useEffect, useRef).
  • Avoid direct global setInterval or clearInterval calls outside the hook.
  • The solution should be performant and not cause unnecessary re-renders in the consuming component.

Notes

  • Consider how to handle the callback function so it always uses the latest state and props. A common pattern for this involves useRef.
  • Think about the dependencies of useEffect. How can you ensure the interval is correctly set up and torn down when delay or callback changes?
  • Ensure that the callback is executed correctly even if it is redefined on each render of the parent component.
Loading editor...
typescript