/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import {useCallback, useState} from 'react'; type ThrottleOptions = { enableThrottling?: boolean; }; type State = { isThrottled: boolean; maxThrottles: boolean; throttle: (callback: () => void, options?: ThrottleOptions) => void; }; export default function useFunctionThrottle( initialDelay: number, numThrottles: number, ): State { const [isThrottled, setIsThrottled] = useState(false); const [lastClickTime, setLastClickTime] = useState(null); const [numTimesThrottled, setNumTimesThrottled] = useState(1); /** * The following function's callback gets throttled when the time between two * executions is less than a threshold. * * The threshold is calculated linearly by multiplying the initial delay * and the number of times the button has been throttled. The button can be * throttled up to numThrottles times. * * The function has an optional flag - enableThrottling - which allows a callsite * to optionally disable throttling. This is useful in cases where throttling may * not be necessary. (e.g. for the Track & Play button, we would only like to * throttle after a stream is aborted.) */ const throttle = useCallback( ( callback: () => void, options: ThrottleOptions = { enableThrottling: true, }, ) => { if (isThrottled) { return; } const currentTime = Date.now(); if (lastClickTime == null) { callback(); setLastClickTime(currentTime); return; } const timeBetweenClicks = currentTime - lastClickTime; const delay = initialDelay * numTimesThrottled; const shouldThrottle = options.enableThrottling && delay > timeBetweenClicks; if (shouldThrottle) { setIsThrottled(true); setTimeout(() => { setIsThrottled(false); }, delay); setNumTimesThrottled(prev => { return prev === numThrottles ? numThrottles : prev + 1; }); } callback(); setLastClickTime(currentTime); }, [initialDelay, numThrottles, isThrottled, lastClickTime, numTimesThrottled], ); return { isThrottled, maxThrottles: numTimesThrottled === numThrottles, throttle, }; }