Slim
Slim

Reputation: 6187

Debounce input change executed for every type change

I want to add a debounce function to not launch the search api for every character:

export const debounce = <F extends ((...args: any) => any)>(
  func: F,
  waitFor: number,
) => {
  let timeout: number = 0;

  const debounced = (...args: any) => {
    clearTimeout(timeout);
    setTimeout(() => func(...args), waitFor);
  };

  return debounced as (...args: Parameters<F>) => ReturnType<F>;
};

Search.tsx:

import React, { useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getAllTasks } from "./taskSlice";
import { ReturnedTask } from "../../api/tasks/index";
import TextField from "@material-ui/core/TextField";
import Autocomplete from "@material-ui/lab/Autocomplete";
import { AppState } from "../../app/rootReducer";
import { debounce } from "../../app/utils";

const SearchTask = () => {
  const dispatch = useDispatch();
  const [open, setOpen] = React.useState(false);
  const debounceOnChange = useCallback(debounce(handleChange, 400), []);
  const { tasks, userId } = useSelector((state: AppState) => ({
    tasks: state.task.tasks,
    userId: state.authentication.user.userId,
  }));

  const options = tasks.length
    ? tasks.map((task: ReturnedTask) => ({ title: task.title }))
    : [];

  const handleOpen = () => setOpen(true);
  const handleClose = () => setOpen(false);

  function handleChange(
    e: React.ChangeEvent<{}>,
    value: string,
  ) {
    const queryParams = [`userId=${userId}`];
    if (value.length) {
      console.log(value);
      queryParams.push(`title=${value}`);
      dispatch(getAllTasks(queryParams));
    }
  }

  return (
    <Autocomplete
      style={{ width: 300 }}
      open={open}
      onOpen={handleOpen}
      onClose={handleClose}
      onInputChange={(e: React.ChangeEvent<{}>, value: string) =>
        debounceOnChange(e, value)}
      getOptionSelected={(option, value) => option.title === value.title}
      getOptionLabel={(option) => option.title}
      options={options}
      renderInput={(params) => (
        <TextField
          {...params}
          label="Search Tasks"
          variant="outlined"
          InputProps={{ ...params.InputProps, type: "search" }}
        />
      )}
    />
  );
};

export default SearchTask;

The actual behavior of the component is launching the api for every character typed. I want to launch the api only after an amount of time. How can I fix it?

Upvotes: 0

Views: 292

Answers (1)

Slim
Slim

Reputation: 6187

As @Jayce444 mentioned in the comment, the the context was not preserved between every call and the function.

export const debounce = <F extends ((...args: any) => any)>(
  func: F,
  waitFor: number,
) => {
  let timeout: NodeJS.Timeout;

  return (...args: any) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), waitFor);
  };
};

Upvotes: 1

Related Questions