Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

119
Vistas
Debouncing and Timeout in React

I have a here a input field that on every type, it dispatches a redux action. I have put a useDebounce in order that it won't be very heavy. The problem is that it says Hooks can only be called inside of the body of a function component. What is the proper way to do it?

useTimeout

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

export default function useTimeout(callback, delay) {
  const callbackRef = useRef(callback);
  const timeoutRef = useRef();

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

  const set = useCallback(() => {
    timeoutRef.current = setTimeout(() => callbackRef.current(), delay);
  }, [delay]);

  const clear = useCallback(() => {
    timeoutRef.current && clearTimeout(timeoutRef.current);
  }, []);

  useEffect(() => {
    set();
    return clear;
  }, [delay, set, clear]);

  const reset = useCallback(() => {
    clear();
    set();
  }, [clear, set]);

  return { reset, clear };
}

useDebounce

import { useEffect } from "react";
import useTimeout from "./useTimeout";

export default function useDebounce(callback, delay, dependencies) {
  const { reset, clear } = useTimeout(callback, delay);
  useEffect(reset, [...dependencies, reset]);
  useEffect(clear, []);
}

Form component

import React from "react";
import TextField from "@mui/material/TextField";
import useDebounce from "../hooks/useDebounce";

export default function ProductInputs(props) {
  const { handleChangeProductName = () => {} } = props;

  return (
    <TextField
      fullWidth
      label="Name"
      variant="outlined"
      size="small"
      name="productName"
      value={formik.values.productName}
      helperText={formik.touched.productName ? formik.errors.productName : ""}
      error={formik.touched.productName && Boolean(formik.errors.productName)}
      onChange={(e) => {
        formik.setFieldValue("productName", e.target.value);
        useDebounce(() => handleChangeProductName(e.target.value), 1000, [
          e.target.value,
        ]);
      }}
    />
  );
}
about 4 years ago · Juan Pablo Isaza
3 Respuestas
Responde la pregunta

0

I don't think React hooks are a good fit for a throttle or debounce function. From what I understand of your question you effectively want to debounce the handleChangeProductName function.

Here's a simple higher order function you can use to decorate a callback function with to debounce it. If the returned function is invoked again before the timeout expires then the timeout is cleared and reinstantiated. Only when the timeout expires is the decorated function then invoked and passed the arguments.

const debounce = (fn, delay) => {
  let timerId;
  return (...args) => {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn(...args), delay);
  }
};

Example usage:

export default function ProductInputs({ handleChangeProductName }) {
  const debouncedHandler = useCallback(debounce(handleChangeProductName, 200), []);

  return (
    <TextField
      fullWidth
      label="Name"
      variant="outlined"
      size="small"
      name="productName"
      value={formik.values.productName}
      helperText={formik.touched.productName ? formik.errors.productName : ""}
      error={formik.touched.productName && Boolean(formik.errors.productName)}
      onChange={(e) => {
        formik.setFieldValue("productName", e.target.value);
        debouncedHandler(e.target.value);
      }}
    />
  );
}

If possible the parent component passing the handleChangeProductName callback as a prop should probably handle creating a debounced, memoized handler, but the above should work as well.

about 4 years ago · Juan Pablo Isaza Denunciar

0

Taking a look at your implementation of useDebounce, and it doesn't look very useful as a hook. It seems to have taken over the job of calling your function, and doesn't return anything, but most of it's implementation is being done in useTimeout, which also not doing much...

In my opinion, useDebounce should return a "debounced" version of callback

Here is my take on useDebounce:

export default function useDebounce(callback, delay) {
  const [debounceReady, setDebounceReady] = useState(true);

  const debouncedCallback = useCallback((...args) => {
    if (debounceReady) {
      callback(...args);
      setDebounceReady(false);
    }
  }, [debounceReady, callback]);

  useEffect(() => {
    if (debounceReady) {
      return undefined;
    }
    const interval = setTimeout(() => setDebounceReady(true), delay);
    return () => clearTimeout(interval);    
  }, [debounceReady, delay]);

  return debouncedCallback;
}

Usage will look something like:

import React from "react";
import TextField from "@mui/material/TextField";
import useDebounce from "../hooks/useDebounce";

export default function ProductInputs(props) {
  const handleChangeProductName = useCallback((value) => {
    if (props.handleChangeProductName) {
      props.handleChangeProductName(value);
    } else {
      // do something else...
    };
  }, [props.handleChangeProductName]);

  const debouncedHandleChangeProductName = useDebounce(handleChangeProductName, 1000);

  return (
    <TextField
      fullWidth
      label="Name"
      variant="outlined"
      size="small"
      name="productName"
      value={formik.values.productName}
      helperText={formik.touched.productName ? formik.errors.productName : ""}
      error={formik.touched.productName && Boolean(formik.errors.productName)}
      onChange={(e) => {
        formik.setFieldValue("productName", e.target.value);
        debouncedHandleChangeProductName(e.target.value);
      }}
    />
  );
}
about 4 years ago · Juan Pablo Isaza Denunciar

0

Debouncing onChange itself has caveats. Say, it must be uncontrolled component, since debouncing onChange on controlled component would cause annoying lags on typing.

Another pitfall, we might need to do something immediately and to do something else after a delay. Say, immediately display loading indicator instead of (obsolete) search results after any change, but send actual request only after user stops typing.

With all this in mind, instead of debouncing callback I propose to debounce sync-up through useEffect:

const [text, setText] = useState('');
const isValueSettled = useIsSettled(text);

useEffect(() => {
  if (isValueSettled) {
    props.onChange(text);
  }
}, [text, isValueSettled]);

...
  <input value={value} onChange={({ target: { value } }) => setText(value)}

And useIsSetlled itself will debounce:

function useIsSettled(value, delay = 500) {
  const [isSettled, setIsSettled] = useState(true);
  const isFirstRun = useRef(true);
  const prevValueRef = useRef(value);

  useEffect(() => {
    if (isFirstRun.current) {
      isFirstRun.current = false;
      return;
    }
    setIsSettled(false);
    prevValueRef.current = value;
    const timerId = setTimeout(() => {
      setIsSettled(true);
    }, delay);
    return () => { clearTimeout(timerId); }
  }, [delay, value]);
  if (isFirstRun.current) {
    return true;
  }
  return isSettled && prevValueRef.current === value;
}

where isFirstRun is obviously save us from getting "oh, no, user changed something" after initial rendering(when value is changed from undefined to initial value).

And prevValueRef.current === value is not required part but makes us sure we will get useIsSettled returning false in the same render run, not in next, only after useEffect executed.

about 4 years ago · Juan Pablo Isaza Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda