58
votes

Comment utiliser l'accélérateur ou l'anti-rebond avec React Hook?

J'essaie d'utiliser la méthode d' throttle de lodash dans un composant fonctionnel, par exemple:

const App = () => {
  const [value, setValue] = useState(0)
  useEffect(throttle(() => console.log(value), 1000), [value])
  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

Étant donné que la méthode à l'intérieur de useEffect est redéclarée à chaque rendu, l'effet de limitation ne fonctionne pas.

Quelqu'un at-il une solution simple?


2 commentaires

Est-ce une option pour vous de définir la fonction étranglée en dehors du composant App et de l'appeler simplement dans la fonction useEffect ?


Oui, j'ai essayé et cela fonctionne, mais dans mon cas, ce n'est pas très élégant, car j'utilise des variables de composant dans la méthode de limitation.


17 Réponses :


65
votes

Après un certain temps, je suis sûr qu'il est beaucoup plus facile de gérer les choses par vous-même avec setTimeout/clearTimeout (et de le déplacer dans un crochet personnalisé séparé) que de travailler avec des assistants fonctionnels. La gestion ultérieure crée des défis supplémentaires juste après que nous ayons appliqué cela à useCallback qui peut être recréé en raison du changement de dépendance, mais nous ne voulons pas réinitialiser le délai d'exécution.

réponse originale ci-dessous

vous pouvez (et probablement en avoir besoin) useRef pour stocker la valeur entre les rendus. Tout comme il est suggéré pour les minuteries

Quelque chose comme ca

  const throttled = useRef(throttle(() => console.log(value), 1000))

  useEffect(throttled.current, [value])

Quant à l' useCallback

Cela peut fonctionner aussi

const throttled = useCallback(throttle(() => console.log(value), 1000), [value]);

Mais si nous essayons de recréer un rappel une fois la value modifiée:

const throttled = useCallback(throttle(newValue => console.log(newValue), 1000), []);

nous pouvons trouver qu'il ne retarde pas l'exécution: une fois que la value est modifiée, le rappel est immédiatement recréé et exécuté.

Je vois donc que useCallback en cas d'exécution retardée ne fournit pas d'avantage significatif. C'est à vous.

[UPD] initialement c'était

const App = () => {
  const [value, setValue] = useState(0)
  const throttled = useRef(throttle((newValue) => console.log(newValue), 1000))

  useEffect(() => throttled.current(value), [value])

  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

mais de cette façon, throttled.current s'est lié à la value initiale (de 0) par la fermeture. Cela n'a donc jamais été changé, même sur les prochains rendus.

Soyez donc prudent lorsque vous useRef fonctions dans useRef raison de la fonction de fermeture.


7 commentaires

useRef devrait être la réponse mais ce code ne semble pas fonctionner. J'essaye de le réparer.


peut-être que j'ai manqué la valeur initiale de cette partie pour l'utilisation La useRef la valeur initiale


Throttle-debounce utilise d'abord DELAY, puis CALLBACK;)


@mikes cela dépend (pour la version de lodash therer sont en leading et trailing options pour config que github.com/lodash/lodash/blob/master/throttle.js )


puisque nous utilisons des hooks, cela signifie qu'il peut être nécessaire de redéfinir l'effet ou le rappel à chaque fois que nous effectuons le rendu et en même temps le débouncing devrait fonctionner. donc ce n'est pas une réponse acceptable


@hossein alipour ne peut pas être plus d'accord avec vous. après presque un an depuis que j'ai répondu maintenant, je vois qu'il est préférable de contrôler les choses directement dans useEffect avec setTimeout/clearTimeout puis en utilisant la fonction d'assistance setTimeout/clearTimeout rebond. Merci d'avoir soulevé cela.


Nous pouvons utiliser useRef pour créer le callback et le conserver, mais je pense qu'il vaut mieux utiliser useCallback même pour passer les variables nécessaires si nécessaire, ce qui sera rarement le cas. Nous pouvons utiliser setValue pour changer la valeur dans useCallback sans ajouter de value au tableau de dépendances et même accéder à la valeur précédente en utilisant setValue(previous => ...) . Si nous avons besoin d'accéder à la valeur directement sans la changer, nous pouvons la passer en argument comme vous le faites avec useRef dans votre exemple comme useCallback(throttle((value) => { ... }, 1000), []) .



3
votes

J'ai écrit deux hooks simples ( use-throttled-effect et use-debounce-effect ) pour ce cas d'utilisation peut-être que cela sera utile pour quelqu'un d'autre à la recherche d'une solution simple.

import React, { useState } from 'react';
import useThrottledEffect  from 'use-throttled-effect';

export default function Input() {
  const [count, setCount] = useState(0);

  useEffect(()=>{
    const interval = setInterval(() => setCount(count=>count+1) ,100);
    return ()=>clearInterval(interval);
  },[])

  useThrottledEffect(()=>{
    console.log(count);     
  }, 1000 ,[count]);

  return (
    {count}
  );
}


0 commentaires

0
votes

Si vous l'utilisez dans handler, je suis assez certain que c'est la manière de le faire.

function useThrottleScroll() {
  const savedHandler = useRef();

  function handleEvent() {}

  useEffect(() => {
    savedHandleEvent.current = handleEvent;
  }, []);

  const throttleOnScroll = useRef(throttle((event) => savedHandleEvent.current(event), 100)).current;

  function handleEventPersistence(event) {
    return throttleOnScroll(event);
  }

  return {
    onScroll: handleEventPersistence,
  };
}


0 commentaires

1
votes

J'utilise quelque chose comme ça et ça marche très bien:

let debouncer = debounce(
  f => f(),
  1000,
  { leading: true }, // debounce one on leading and one on trailing
);

function App(){
   let [state, setState] = useState();

   useEffect(() => debouncer(()=>{
       // you can use state here for new state value
   }),[state])

   return <div />
}


1 commentaires

d'où vient debounce() ?



12
votes

Cela pourrait être un petit crochet personnalisé, comme ceci:

useDebounce.js

import React, { useEffect } from 'react';

import useDebounce from '/path/to/useDebounce';

const App = (props) => {
    const [state, setState] = useState({title: ''});    
    const debouncedTitle = useDebounce(state.title, 1000);

    useEffect(() => {
        // do whatever you want with state.title/debouncedTitle
    }, [debouncedTitle]);        

    return (
        // ...
    );
}
// ...

Exemple d'utilisation:

import React, { useState, useEffect } from 'react';

export default (value, timeout) => {
    const [state, setState] = useState(value);

    useEffect(() => {
        const handler = setTimeout(() => setState(value), timeout);

        return () => clearTimeout(handler);
    }, [value, timeout]);

    return state;
}

Remarque: comme vous le savez probablement, useEffect s'exécute toujours sur le rendu initial, et à cause de cela, si vous utilisez ma réponse, vous verrez probablement le rendu de votre composant s'exécuter deux fois, ne vous inquiétez pas, il vous suffit d'écrire un autre hook personnalisé. consultez mon autre réponse pour plus d'informations.


0 commentaires

0
votes

Dans mon cas, j'avais également besoin de réussir l'événement. Je suis allé avec ceci:

const MyComponent = () => {
  const handleScroll = useMemo(() => {
    const throttled = throttle(e => console.log(e.target.scrollLeft), 300);
    return e => {
      e.persist();
      return throttled(e);
    };
  }, []);
  return <div onScroll={handleScroll}>Content</div>;
};


0 commentaires

18
votes

J'ai créé mon propre hook personnalisé appelé useDebouncedEffect qui attendra d'effectuer un useEffect jusqu'à ce que l'état ne soit pas mis à jour pendant la durée du délai.

Dans cet exemple, votre effet se connectera à la console après avoir arrêté de cliquer sur le bouton pendant 1 seconde.

App.jsx
import { useCallback, useEffect } from "react";

export const useDebouncedEffect = (effect, delay , deps) => {
    const callback = useCallback(effect, deps);

    useEffect(() => {
        const handler = setTimeout(() => {
            callback();
        }, delay);

        return () => {
            clearTimeout(handler);
        };
    }, [callback, delay]);
}
useDebouncedEffect.js
import { useState } from "react";
import { useDebouncedEffect } from "./useDebouncedEffect";

const App = () => {
  const [value, setValue] = useState(0)

  useDebouncedEffect(() => console.log(value), 1000, [value])

  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}


0 commentaires

8
votes

useThrottle , useDebounce

Comment utiliser les deux

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>

useThrottle ( Lodash )

const App = () => {
  const [value, setValue] = useState(0);
  useThrottle(() => console.log(value), 1000, [value]);
  return (
    <div>
      <button onClick={() => setValue(value + 1)}>{value}</button>
      <p>value will be logged at most once per second.</p>
    </div>
  );
};

function useThrottle(cb, delay, additionalDeps) {
  const options = { leading: true, trailing: false }; // pass custom lodash options
  const cbRef = useRef(cb);
  const throttledCb = useCallback(
    _.throttle((...args) => cbRef.current(...args), delay, options),
    [delay]
  );
  useEffect(() => {
    cbRef.current = cb;
  });
  // set additionalDeps to execute effect, when other values change (not only on delay change)
  useEffect(throttledCb, [throttledCb, ...additionalDeps]);
}

ReactDOM.render(<App />, document.getElementById("root"));

const App = () => {
  // useEffect now is contained inside useThrottle
  useThrottle(() => console.log(value), 1000, [value]);
  // ...
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>

useDebounce ( Lodash )

const debounceImpl = (cb, delay) => {
  let isDebounced = null;
  return (...args) => {
    clearTimeout(isDebounced);
    isDebounced = setTimeout(() => cb(...args), delay);
  };
};

const throttleImpl = (cb, delay) => {
  let isThrottled = false;
  return (...args) => {
    if (isThrottled) return;
    isThrottled = true;
    cb(...args);
    setTimeout(() => {
      isThrottled = false;
    }, delay);
  };
};

const App = () => {
  const [value, setValue] = useState(0);
  const invokeThrottled = useThrottle(
    () => console.log("throttled", value),
    1000
  );
  const invokeDebounced = useDebounce(
    () => console.log("debounced", value),
    1000
  );
  useEffect(invokeThrottled, [value]);
  useEffect(invokeDebounced, [value]);
  return <button onClick={() => setValue(value + 1)}>{value}</button>;
};

function useThrottle(cb, delay) {
  const cbRef = useRef(cb);
  useEffect(() => {
    cbRef.current = cb;
  });
  return useCallback(
    throttleImpl((...args) => cbRef.current(...args), delay),
    [delay]
  );
}

function useDebounce(cb, delay) {
  const cbRef = useRef(cb);
  useEffect(() => {
    cbRef.current = cb;
  });
  return useCallback(
    debounceImpl((...args) => cbRef.current(...args), delay),
    [delay]
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>
const App = () => {
  const [value, setValue] = useState(0);
  const invokeDebounced = useDebounce(
    () => console.log("debounced", value),
    1000
  );
  useEffect(invokeDebounced, [value]);
  return (
    <div>
      <button onClick={() => setValue(value + 1)}>{value}</button>
      <p> Logging is delayed until after 1 sec. has elapsed since the last invocation.</p>
    </div>
  );
};

function useDebounce(cb, delay) {
  const options = {
    leading: false,
    trailing: true
  };
  const inputsRef = useRef(cb);
  const isMounted = useIsMounted();
  useEffect(() => {
    inputsRef.current = { cb, delay };
  });

  return useCallback(
    _.debounce(
      (...args) => {
        // Don't execute callback, if (1) component in the meanwhile 
        // has been unmounted or (2) delay has changed
        if (inputsRef.current.delay === delay && isMounted())
          inputsRef.current.cb(...args);
      },
      delay,
      options
    ),
    [delay, _.debounce]
  );
}

function useIsMounted() {
  const isMountedRef = useRef(true);
  useEffect(() => {
    return () => {
      isMountedRef.current = false;
    };
  }, []);
  return () => isMountedRef.current;
}

ReactDOM.render(<App />, document.getElementById("root"));

Personnalisations

1. Vous pouvez remplacer Lodash par votre propre code d' throttle ou debounce - debounce , comme:

import _ from "lodash"

function useDebounce(cb, delay) {
  // ...
  const inputsRef = useRef(cb); // mutable ref like with useThrottle
  useEffect(() => { inputsRef.current = { cb, delay }; }); //also track cur. delay
  return useCallback(
    _.debounce((...args) => {
        // Debounce is an async callback. Cancel it, if in the meanwhile
        // (1) component has been unmounted (see isMounted in snippet)
        // (2) delay has changed
        if (inputsRef.current.delay === delay && isMounted())
          inputsRef.current.cb(...args);
      }, delay, options
    ),
    [delay, _.debounce]
  );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>

2. useThrottle peut être raccourci, s'il est toujours utilisé avec useEffect (idem pour useDebounce ):

const App = () => {
  const [value, setValue] = useState(0);
  const invokeDebounced = useThrottle(
    () => console.log("changed throttled value:", value),
    1000
  );
  useEffect(invokeDebounced, [value]);
  return (
    <div>
      <button onClick={() => setValue(value + 1)}>{value}</button>
      <p>value will be logged at most once per second.</p>
    </div>
  );
};

function useThrottle(cb, delay) {
  const options = { leading: true, trailing: false }; // pass custom lodash options
  const cbRef = useRef(cb);
  useEffect(() => {
    cbRef.current = cb;
  });
  return useCallback(
    _.throttle((...args) => cbRef.current(...args), delay, options),
    [delay]
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

import _ from "lodash"

function useThrottle(cb, delay) {
  const options = { leading: true, trailing: false }; // add custom lodash options
  const cbRef = useRef(cb);
  // use mutable ref to make useCallback/throttle not depend on `cb` dep
  useEffect(() => { cbRef.current = cb; });
  return useCallback(
    _.throttle((...args) => cbRef.current(...args), delay, options),
    [delay]
  );
}
const App = () => {
  const [value, setValue] = useState(0);
  // called at most once per second (same API with useDebounce)
  const throttledCb = useThrottle(() => console.log(value), 1000);
  // usage with useEffect: invoke throttledCb on value change
  useEffect(throttledCb, [value]);
  // usage as event handler
  <button onClick={throttledCb}>log value</button>
  // ... other render code
};

2 commentaires

Pourquoi utiliser useEffect(() => { cbRef.current = cb; }); sans aucune dépendance? Cela signifie que nous exécutons un effet sur chaque re-rendu, alors pourquoi ne pas simplement assigner sans useEffect?


Bonne question - elle est destinée à toujours contenir le rappel le plus récent dans cbRef . Une référence mutable peut être utilisée comme une variable d'instance pour les Hooks - voici un exemple avec setInterval du blog Overreacted. La phase de rendu doit également être pure sans effets secondaires, par exemple pour être compatible avec le mode concurrent React. C'est pourquoi nous enveloppons l'affectation dans useEffect .



2
votes

En utilisant la fonction anti-rebond de lodash, voici ce que je fais:

<input value={value} onChange={debouncedGetUsers} />

Dans votre JSX:

import debounce from 'lodash/debounce'

// The function that we want to debounce, for example the function that makes the API calls
const getUsers = (event) => {
// ...
}


// The magic!
const debouncedGetUsers = useCallback(debounce(getUsers, 500), [])


0 commentaires

0
votes

Je suis assez en retard pour cela, mais voici un moyen de setState()

/**
 * Like React.setState, but debounces the setter.
 * 
 * @param {*} initialValue - The initial value for setState().
 * @param {int} delay - The debounce delay, in milliseconds.
 */
export const useDebouncedState = (initialValue, delay) => {
  const [val, setVal] = React.useState(initialValue);
  const timeout = React.useRef();
  const debouncedSetVal = newVal => {
    timeout.current && clearTimeout(timeout.current);
    timeout.current = setTimeout(() => setVal(newVal), delay);
  };

  React.useEffect(() => () => clearTimeout(timeout.current), []);
  return [val, debouncedSetVal];
};


0 commentaires

2
votes

Ceci est mon useDebounce :

const TIMEOUT = 500; // wait 500 milliseconds;

export function AppContainer(props) {
    const { dataId } = props;
    const [data, setData] = useState(null);
    //
    useDebounce(
        async () => {
            data = await loadDataFromAPI(dataId);
            setData(data);
        }, 
        TIMEOUT, 
        [dataId]
    );
    //
}

Et vous pouvez l'utiliser comme ceci:

export function useDebounce(callback, timeout, deps) {
    const timeoutId = useRef();

    useEffect(() => {
        clearTimeout(timeoutId.current);
        timeoutId.current = setTimeout(callback, timeout);

        return () => clearTimeout(timeoutId.current);
    }, deps);
}


0 commentaires

0
votes

J'écris un simple hook useDebounce qui prend en compte le nettoyage, tout comme useEffect fonctionne.

const debounceChange = useDebounce(function (e) {
    console.log("debounced text change: " + e.target.value);
  }, 500);
  // can't use debounceChange directly, since react using event pooling
  function deboucnedCallback(e) {
    e.persist();
    debounceChange(e);
  }

// later the jsx
<input onChange={deboucnedCallback} />

l'essentiel est ici: https://gist.github.com/sophister/9cc74bb7f0509bdd6e763edbbd21ba64

et ceci est une démo en direct: https://codesandbox.io/s/react-hook-debounce-demo-mgr89?file=/src/App.js

utilisation:

import { useState, useEffect, useRef, useCallback } from "react";

export function useDebounceState<T>(initValue: T, delay: number) {
  const [value, setValue] = useState<T>(initValue);
  const timerRef = useRef(null);
  // reset timer when delay changes
  useEffect(
    function () {
      if (timerRef.current) {
        clearTimeout(timerRef.current);
        timerRef.current = null;
      }
    },
    [delay]
  );
  const debounceSetValue = useCallback(
    function (val) {
      if (timerRef.current) {
        clearTimeout(timerRef.current);
        timerRef.current = null;
      }
      timerRef.current = setTimeout(function () {
        setValue(val);
      }, delay);
    },
    [delay]
  );
  return [value, debounceSetValue];
}

interface DebounceOptions {
  imediate?: boolean;
  initArgs?: any[];
}

const INIT_VALUE = -1;
export function useDebounce(fn, delay: number, options: DebounceOptions = {}) {
  const [num, setNum] = useDebounceState(INIT_VALUE, delay);
  // save actual arguments when fn called
  const callArgRef = useRef(options.initArgs || []);
  // save real callback function
  const fnRef = useRef(fn);
  // wrapped function
  const trigger = useCallback(function () {
    callArgRef.current = [].slice.call(arguments);
    setNum((prev) => {
      return prev + 1;
    });
  }, []);
  // update real callback
  useEffect(function () {
    fnRef.current = fn;
  });
  useEffect(
    function () {
      if (num === INIT_VALUE && !options.imediate) {
        // prevent init call
        return;
      }
      return fnRef.current.apply(null, callArgRef.current);
    },
    [num, options.imediate]
  );
  return trigger;
}


0 commentaires

1
votes

Je voudrais rejoindre la fête avec mon entrée useState rebond en utilisant useState :

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
// import { React, useState } from 'react' // nomral import
const { useState } = React // inline import

const App = () => {
  const [t, setT] = useState();
  
  const handleChange = ({ target: { value } }) => {
    clearTimeout(t)
    setT(setTimeout(() => console.log(value), 700))
  }
  
  return (
    <input onChange={handleChange} />
  )
}

ReactDOM.render(<App />, document.getElementById('root'))


0 commentaires

-1
votes

Je ne sais pas si vous utilisez Redux ou non, probablement pas. Cependant, j'ai eu un problème similaire et je l'ai résolu avec Saga.

let throttledSort=(e) => action('THROTTLED_SORT', e.target.cellIndex);

Ce qu'il fait précisément est de limiter l'envoi de l'action SORT. Dans votre composant, vous devez utiliser THROTTLED_SORT et pas seulement TRIER

import { put, all, takeLatest } from "redux-saga/effects";

const delay = (ms) => new Promise((res) => setTimeout(res, ms));

function* throttledSort(e) {
  yield delay(400);
  yield put({ type: "SORT", payload: e.payload });
}

function* watchSort() {
  yield takeLatest("THROTTLED_SORT", throttledSort);
}

export default function* rootSaga() {
  yield all([watchSort()]);
}

Ce n'est pas précisément la réponse à votre question mais cela pourrait être utile, de toute façon (si vous pensez que c'est trop loin, veuillez ne pas sous-noter)


0 commentaires

0
votes

Voici un véritable crochet d'accélérateur. Vous pouvez utiliser dans un écran ou un composant pour toutes les fonctions que vous souhaitez contrôler, et elles partageront la même accélération. Ou vous pouvez appeler useThrottle() plusieurs fois et avoir des manettes différentes pour des fonctions individuelles.

Utilisez comme ceci:

import { useCallback, useState } from "react";

// Throttles all callbacks on a component within the same throttle.  
// All callbacks passed in will share the same throttle.

const THROTTLE_DURATION = 500;

export default (callbacks: Array<() => any>) => {
    const [isWaiting, setIsWaiting] = useState(false);

    const throttledCallbacks = callbacks.map((callback) => {
        return useCallback(() => {
            if (!isWaiting) {
                callback()
                setIsWaiting(true)
                setTimeout(() => {
                    setIsWaiting(false)
                }, THROTTLE_DURATION);
            }
        }, [isWaiting]);
    })

    return throttledCallbacks;
}

Et le crochet lui-même:

import useThrottle from '../hooks/useThrottle';

const [navigateToSignIn, navigateToCreateAccount] = useThrottle([
        () => { navigation.navigate(NavigationRouteNames.SignIn) },
        () => { navigation.navigate(NavigationRouteNames.CreateAccount) }
    ])


0 commentaires

0
votes

Voici un crochet simple pour annuler vos appels.

Pour utiliser le code ci-dessous, tout ce que vous avez à faire est de le déclarer comme tel

const { debounceRequest } = useDebounce(someFn);

Et puis appelez-le ainsi

import React from "react";

const useDebounce = (callbackFn: () => any, timeout: number = 500) => {
const [sends, setSends] = React.useState(0);

const stabilizedCallbackFn = React.useCallback(callbackFn, [callbackFn]);

const debounceRequest = () => {
  setSends(sends + 1);
};

// 1st send, 2nd send, 3rd send, 4th send ...
// when the 2nd send comes, then 1st set timeout is cancelled via clearInterval
// when the 3rd send comes, then 2nd set timeout is cancelled via clearInterval
// process continues till timeout has passed, then stabilizedCallbackFn gets called
// return () => clearInterval(id) is critical operation since _this_ is what cancels 
//  the previous send.
// *🎗 return () => clearInterval(id) is called for the previous send when a new send 
// is sent. Essentially, within the timeout all but the last send gets called.

React.useEffect(() => {
  if (sends > 0) {
     const id = window.setTimeout(() => {
       stabilizedCallbackFn();
       setSends(0);
     }, timeout);
     return () => {
      return window.clearInterval(id);
     };
  }
 }, [stabilizedCallbackFn, sends, timeout]);

 return {
   debounceRequest,
 };
};

export default useDebounce;

La mise en œuvre est illustrée ci-dessous

debounceRequest(); 


0 commentaires

0
votes
const useDebounce = (func: any) => {
    const debounceFunc = useRef(null);

    useEffect(() => {
        if (func) {
            // @ts-ignore
            debounceFunc.current = debounce(func, 1000);
        }
    }, []);

    const debFunc = () => {
        if (debounceFunc.current) {
            return debounceFunc.current;
        }
        return func;
    };
    return debFunc();
};

0 commentaires