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. Ifnullorundefined, the interval should be cleared.
- The hook should manage the
setIntervalandclearIntervalcalls 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
useIntervalhook should pick up the latest value on the next execution of the callback. - When the
delayprop 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
delayis a positive number, thecallbackfunction should be invoked everydelaymilliseconds. - When
delayis set tonullorundefined, the interval should be stopped. - If the
callbackfunction 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
delayis 0? (The behavior should be consistent withsetIntervalwhere 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
callbackfunction 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
callbackfunction must be a valid JavaScript function. - The
delaymust be a number (milliseconds) ornull/undefined. - The
useIntervalhook must be implemented using React Hooks (useState,useEffect,useRef). - Avoid direct global
setIntervalorclearIntervalcalls outside the hook. - The solution should be performant and not cause unnecessary re-renders in the consuming component.
Notes
- Consider how to handle the
callbackfunction so it always uses the latest state and props. A common pattern for this involvesuseRef. - Think about the dependencies of
useEffect. How can you ensure the interval is correctly set up and torn down whendelayorcallbackchanges? - Ensure that the
callbackis executed correctly even if it is redefined on each render of the parent component.