You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在React中使用Debounce实现搜索的正确方式——解决TypeError: Expected a function报错问题

Hey there, let's break down what's causing that TypeError: Expected a function and walk through the correct way to implement debounced search with React, Material UI, Formik, and Lodash.

Why You're Seeing the Error

The core issue here is that you're passing the result of a function call to Lodash's debounce instead of passing a function itself.

Look at this line in your code:

const debounceLoadData = useCallback( debounce(dispatch(getPersons(10, 1, filter)), 1000), [] );

When you write dispatch(getPersons(10, 1, filter)), you're immediately executing the dispatch call—this returns an action object (or undefined, depending on your Redux setup), not a reusable function. Lodash's debounce expects its first argument to be a function it can wrap and delay, hence the error.

Also, you've declared debounceLoadData twice, which is a syntax error that needs fixing.

Correct Implementation

Let's fix this with a clean, maintainable approach:

1. Wrap Debounce Around a Proper Function

First, we need to pass an actual function to debounce. There are two solid patterns for this, but the first one is more straightforward for search scenarios:

Pattern 1: Pass the Search Value Directly to the Debounced Function

This way, the debounced function uses the exact search value you pass to it, avoiding issues with stale state references:

import { debounce } from 'lodash';
import { useCallback, useState } from 'react';

// ...

const [filter, setFilter] = useState("");

// Use useCallback to keep the debounced function reference stable across re-renders
const debounceLoadData = useCallback(
  debounce((searchValue) => {
    // Skip empty searches if needed
    if (!searchValue.trim()) {
      // Clear results or handle empty state here
      return;
    }
    // Now we call dispatch inside the debounced function
    dispatch(getPersons(10, 1, searchValue));
  }, 1000),
  [dispatch] // Only re-create if dispatch changes (rare in most setups)
);

const onSearchVoter = (value) => {
  setFilter(value);
  debounceLoadData(value); // Pass the current search value to the debounced function
};
Pattern 2: Use useRef to Preserve Debounced Function (For State-Dependent Logic)

If you need the debounced function to rely on the latest filter state (instead of passing the value directly), use a ref to avoid re-creating the debounced function every time filter changes:

import { debounce } from 'lodash';
import { useCallback, useState, useRef } from 'react';

// ...

const [filter, setFilter] = useState("");
const debounceRef = useRef();

const debounceLoadData = useCallback(() => {
  // Initialize the debounced function once
  if (!debounceRef.current) {
    debounceRef.current = debounce(() => {
      dispatch(getPersons(10, 1, filter));
    }, 1000);
  }
  // Trigger the debounced function
  debounceRef.current();
}, [dispatch, filter]);

const onSearchVoter = (value) => {
  setFilter(value);
  debounceLoadData();
};

Pattern 1 is preferred for search because it avoids potential stale state issues and is easier to reason about.

2. Clean Up the Autocomplete Handler

Your onInputChange doesn't need to be async since there's no await inside—simplify it:

onInputChange={(event, value) => {
  onSearchVoter(value);
}}

3. Full Optimized Code Example

Putting it all together, here's what the fixed component might look like:

import { debounce } from 'lodash';
import { useCallback, useState, useEffect } from 'react';
import { Autocomplete, TextField } from '@mui/material';
import { useFormikContext } from 'formik';

const VoterSearch = () => {
  const { values, setFieldValue, touched, errors, handleBlur } = useFormikContext();
  const [filter, setFilter] = useState("");
  const [persons, setPersons] = useState([]);

  const debounceLoadData = useCallback(
    debounce((searchValue) => {
      if (!searchValue.trim()) {
        setPersons([]);
        return;
      }
      // Adjust this based on your Redux async logic (e.g., if getPersons returns a promise)
      dispatch(getPersons(10, 1, searchValue))
        .then((response) => setPersons(response.data))
        .catch((err) => console.error('Search failed:', err));
    }, 1000),
    [dispatch]
  );

  // Clean up debounce on unmount to avoid memory leaks
  useEffect(() => {
    return () => debounceLoadData.cancel();
  }, [debounceLoadData]);

  const onSearchVoter = (value) => {
    setFilter(value);
    debounceLoadData(value);
  };

  return (
    <Autocomplete
      value={values.voter_id}
      options={persons || []}
      getOptionSelected={(option, value) => option === value}
      getOptionLabel={(person) => 
        person ? [person?.fname, person?.mname, person?.lname].filter(Boolean).join(" ") : "" 
      }
      onChange={(e, value) => {
        setFieldValue("voter_id", value ?? "");
      }}
      onInputChange={(event, value) => {
        onSearchVoter(value);
      }}
      renderInput={(params) => (
        <TextField
          {...params}
          name="voter_id"
          label="Voter"
          variant="outlined"
          onBlur={handleBlur}
          helperText={touched.voter_id ? errors.voter_id : ""}
          error={touched.voter_id && Boolean(errors.voter_id)}
          fullWidth
        />
      )}
    />
  );
};

export default VoterSearch;

Quick Extra Tip

Always clean up your debounced function when the component unmounts (like we did with useEffect and debounceLoadData.cancel())—this prevents unnecessary network requests or state updates after the component is gone.

内容的提问来源于stack exchange,提问作者Joseph

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 10:52:29