javascript - 当用户停止输入搜索框时执行 api 请求

标签 javascript reactjs redux react-redux components

我正在构建一个搜索字段,该字段根据用户输入从数据库中获取,但我遇到了一些困难。目前,它在每次击键时都获取数据,这并不理想。我查看了不同的答案,似乎最好的选择是在 componentDidUpdate() 中执行此操作,并通过 setTimeout() 获取输入感觉的引用以将其与当前值进行比较。

我已经试过了,但我仍然在每次击键时获取数据,不知道为什么?请参阅下面的组件示例:


class ItemsHolder extends Component {
    componentDidMount() {
        //ensures the page is reloaded at the top when routing
        window.scrollTo(0, 0);
        this.props.onFetchItems(this.props.search);
    }

    componentDidUpdate(prevProps, prevState) {
        if (prevProps.search !== this.props.search) {
            console.log(
                this.props.search ===
                    this.props.searchRef.current.props.value.toUpperCase()
            );
            setTimeout(() => {
                console.log(
                    this.props.search ===
                        this.props.searchRef.current.props.value.toUpperCase()
                );
                if (
                    this.props.search ===
                    this.props.searchRef.current.props.value.toUpperCase()
                ) {
                    this.props.onFetchItems(this.props.search, this.props.category);
                }
            }, 500);
        }
    }

我正在使用 Redux 进行状态管理。这是获取项目时调用的函数:

export const fetchItemsFromServer = (search) => {
    return (dispatch) => {
        dispatch(fetchItemsStart());
        const query =
            search.length === 0 ? '' : `?orderBy="country"&equalTo="${search}"`;
        axios
            .get('/items.json' + query)
            .then((res) => {
                const fetchedItems = [];
                for (let item in res.data) {
                    fetchedItems.push({
                        ...res.data[item],
                        id: item,
                    });
                }
                dispatch(fetchItemsSuccess(fetchedItems));
            })
            .catch((error) => {
                dispatch(fetchItemsFail(error));
            });
    };
};

这就是我在搜索组件中设置 ref 的方式:

class Search extends Component {
    constructor(props) {
        super(props);
        this.searchInput = React.createRef();
    }
    componentDidMount() {
        this.props.onSetRef(this.searchInput);
    }

    render() {
        return (
            <Input
                ref={this.searchInput}
                toolbar
                elementType={this.props.inputC.elementType}
                elementConfig={this.props.inputC.elementConfig}
                value={this.props.inputC.value}
                changed={(event) => this.props.onChangedHandler(event)}
            />
        );
    }
}

根据教程,我发现这应该可行。供您引用,请参阅本教程中的代码。我不明白为什么上面的方法不起作用。唯一的区别是教程使用了钩子(Hook)。

const Search = React.memo(props => {
  const { onLoadIngredients } = props;
  const [enteredFilter, setEnteredFilter] = useState('');
  const inputRef = useRef();

  useEffect(() => {
    const timer = setTimeout(() => {
      if (enteredFilter === inputRef.current.value) {
        const query =
          enteredFilter.length === 0
            ? ''
            : `?orderBy="title"&equalTo="${enteredFilter}"`;
        fetch(
          'https://react-hooks-update.firebaseio.com/ingredients.json' + query
        )
          .then(response => response.json())
          .then(responseData => {
            const loadedIngredients = [];
            for (const key in responseData) {
              loadedIngredients.push({
                id: key,
                title: responseData[key].title,
                amount: responseData[key].amount
              });
            }
            onLoadIngredients(loadedIngredients);
          });
      }
    }, 500);
    return () => {
      clearTimeout(timer);
    };
  }, [enteredFilter, onLoadIngredients, inputRef]);

遵循 debounceInput 的建议:

import React, { Component } from 'react';
// import classes from './Search.css';
import Input from '../../UI/Input/Input';
// redux
import * as actions from '../../../store/actions/index';
import { connect } from 'react-redux';

class Search extends Component {
    componentDidUpdate(prevProps, prevState) {
        if (prevProps.search !== this.props.search) {
            this.props.onFetchItems(this.props.search, this.props.category);
        }
    }

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

    render() {
        return (
            <Input
                toolbar
                elementType={this.props.inputC.elementType}
                elementConfig={this.props.inputC.elementConfig}
                value={this.props.inputC.value}
                changed={(event) =>
                    this.debounceInput(this.props.onChangedHandler(event), 500)
                }
            />
        );
    }
}

const mapStateToProps = (state) => {
    return {
        inputC: state.filtersR.inputConfig,
        search: state.filtersR.search,
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        onChangedHandler: (event) => dispatch(actions.inputHandler(event)),
        onFetchItems: (search, category) =>
            dispatch(actions.fetchItemsFromServer(search, category)),
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(Search);

这里是得到帮助后的最终解决方案:

import React, { Component } from 'react';
// import classes from './Search.css';
import Input from '../../UI/Input/Input';
// redux
import * as actions from '../../../store/actions/index';
import { connect } from 'react-redux';

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

class Search extends Component {
    componentDidUpdate(prevProps, _prevState) {
        if (prevProps.search !== this.props.search) {
            this.responseHandler();
        }
    }

    responseHandler = debounceInput(() => {
        this.props.onFetchItems(this.props.search, this.props.category);
    }, 1000);

    render() {
        return (
            <Input
                toolbar
                elementType={this.props.inputC.elementType}
                elementConfig={this.props.inputC.elementConfig}
                value={this.props.inputC.value}
                changed={(event) => this.props.onChangedHandler(event)}
            />
        );
    }
}

const mapStateToProps = (state) => {
    return {
        inputC: state.filtersR.inputConfig,
        search: state.filtersR.search,
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        onChangedHandler: (event) => dispatch(actions.inputHandler(event)),
        onFetchItems: (search, category) =>
            dispatch(actions.fetchItemsFromServer(search, category)),
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(Search);

最佳答案

您真的只需要去抖输入的 onChange 处理程序,或者更好的是,实际执行异步工作的函数。

非常简单的去抖动高阶函数:

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

使用示例:

fetchData = debounce(() => fetch(.....).then(....), 500);

componentDidUpdate(.......) {
  // input value different, call fetchData
}

<Input
  toolbar
  elementType={this.props.inputC.elementType}
  elementConfig={this.props.inputC.elementConfig}
  value={this.props.inputC.value}
  changed={this.props.onChangedHandler}
/>

演示代码

Edit execute-api-request-when-user-stops-typing-search-box

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

const fetch = (url, options) => {
  console.log("Fetching", url);
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("Fetch Resolved");
      resolve(`response - ${Math.floor(Math.random() * 1000)}`);
    }, 2000);
  });
};

export default class App extends Component {
  state = {
    search: "",
    response: ""
  };

  changeHandler = (e) => {
    const { value } = e.target;
    console.log("search", value);
    this.setState({ search: value });
  };

  fetchData = debounce(() => {
    const { search } = this.state;
    const query = search.length ? `?orderBy="country"&equalTo="${search}"` : "";

    fetch(
      "https://react-hooks-update.firebaseio.com/ingredients.json" + query
    ).then((response) => this.setState({ response }));
  }, 500);

  componentDidUpdate(prevProps, prevState) {
    if (prevState.search !== this.state.search) {
      if (this.state.response) {
        this.setState({ response: "" });
      }
      this.fetchData();
    }
  }

  render() {
    const { response, search } = this.state;
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>

        <label>
          Search
          <input type="text" value={search} onChange={this.changeHandler} />
        </label>

        <div>Debounced Response: {response}</div>
      </div>
    );
  }
}

关于javascript - 当用户停止输入搜索框时执行 api 请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66394801/

相关文章:

javascript - 如何在 React.js 中遍历对象并创建子组件

reactjs - 在挂载组件上对 react 表进行排序

javascript - 在哪里将我的 mongodb Atlas 连接到我的 React 应用程序?

javascript - 将选项卡关闭到左侧,同时排除固定选项卡

javascript - 将关联帐户的 Stripe 费用 ID 和帐户 ID 公开给客户端是否安全?

javascript - 如何正确使用 redux 和 react-native 导航

javascript - 我如何授权与 redux react 的路线?

reactjs - 我可以在react组件的render()方法中调用redux的store.subscribe方法吗?

javascript - 如何从 Trello API 获取卡片创建日期?

javascript - React Router vs Redux 优先路由