当用户停止输入搜索框时执行 api 请求 [英] Execute api request when user stops typing search box

查看:59
本文介绍了当用户停止输入搜索框时执行 api 请求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

<预><代码>类 ItemsHolder 扩展组件 {componentDidMount() {//确保页面在路由时在顶部重新加载window.scrollTo(0, 0);this.props.onFetchItems(this.props.search);}componentDidUpdate(prevProps, prevState) {if (prevProps.search !== this.props.search) {控制台.日志(this.props.search ===this.props.searchRef.current.props.value.toUpperCase());setTimeout(() => {控制台.日志(this.props.search ===this.props.searchRef.current.props.value.toUpperCase());如果 (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) =>{返回(调度)=>{调度(fetchItemsStart());常量查询 =search.length === 0 ?'' : `?orderBy="country"&equalTo="${search}"`;公理.get('/items.json' + 查询).then((res) => {const fetchedItems = [];for(让 res.data 中的项目){fetchedItems.push({...res.data[item],编号:项目,});}dispatch(fetchItemsSuccess(fetchedItems));}).catch((错误) => {调度(fetchItemsFail(错误));});};};

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

class Search extends Component {构造函数(道具){超级(道具);this.searchInput = React.createRef();}componentDidMount() {this.props.onSetRef(this.searchInput);}使成为() {返回 (<输入ref={this.searchInput}工具栏elementType={this.props.inputC.elementType}elementConfig={this.props.inputC.elementConfig}值={this.props.inputC.value}改变={(事件)=>this.props.onChangedHandler(event)}/>);}}

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

const Search = React.memo(props => {const { onLoadIngredients } = props;const [enteredFilter, setEnteredFilter] = useState('');const inputRef = useRef();useEffect(() => {const timer = setTimeout(() => {if (enteredFilter === inputRef.current.value) {常量查询 =输入过滤器.长度 === 0?'': `?orderBy="title"&equalTo="${enteredFilter}"`;拿来('https://react-hooks-update.firebaseio.com/ingredients.json' + 查询).then(response => response.json()).then(responseData => {常量加载的成分 = [];for (const key in responseData) {加载的成分.push({身份证:钥匙,标题: responseData[key].title,金额: responseData[key].amount});}onLoadIngredients(loadedIngredients);});}}, 500);返回 () =>{清除超时(定时器);};}, [enteredFilter, onLoadIngredients, inputRef]);

以下对 debounceInput 的建议:

import React, { Component } from 'react';//从 './Search.css' 导入类;从'../../UI/Input/Input'导入输入;//还原从'../../../store/actions/index'导入*作为动作;从'react-redux'导入{连接};类搜索扩展组件{componentDidUpdate(prevProps, prevState) {if (prevProps.search !== this.props.search) {this.props.onFetchItems(this.props.search, this.props.category);}}debounceInput = (fn, delay) =>{让 timerId;返回 (...args) =>{clearTimeout(timerId);timerId = setTimeout(() => fn(...args), delay);};};使成为() {返回 (<输入工具栏elementType={this.props.inputC.elementType}elementConfig={this.props.inputC.elementConfig}值={this.props.inputC.value}改变={(事件)=>this.debounceInput(this.props.onChangedHandler(event), 500)}/>);}}const mapStateToProps = (状态) =>{返回 {inputC: state.filtersR.inputConfig,搜索:state.filtersR.search,};};const mapDispatchToProps = (调度) =>{返回 {onChangedHandler: (事件) =>dispatch(actions.inputHandler(event)),onFetchItems:(搜索,类别)=>dispatch(actions.fetchItemsFromServer(search, category)),};};导出默认连接(mapStateToProps,mapDispatchToProps)(搜索);

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

import React, { Component } from 'react';//从 './Search.css' 导入类;从'../../UI/Input/Input'导入输入;//还原从'../../../store/actions/index'导入*作为动作;从'react-redux'导入{连接};const debounceInput = (fn, delay) =>{让 timerId;返回 (...args) =>{clearTimeout(timerId);timerId = setTimeout(() => fn(...args), delay);};};类搜索扩展组件{componentDidUpdate(prevProps, _prevState) {if (prevProps.search !== this.props.search) {this.responseHandler();}}responseHandler = debounceInput(() => {this.props.onFetchItems(this.props.search, this.props.category);}, 1000);使成为() {返回 (<输入工具栏elementType={this.props.inputC.elementType}elementConfig={this.props.inputC.elementConfig}值={this.props.inputC.value}改变={(事件)=>this.props.onChangedHandler(event)}/>);}}const mapStateToProps = (状态) =>{返回 {inputC: state.filtersR.inputConfig,搜索:state.filtersR.search,};};const mapDispatchToProps = (调度) =>{返回 {onChangedHandler: (事件) =>dispatch(actions.inputHandler(event)),onFetchItems:(搜索,类别)=>dispatch(actions.fetchItemsFromServer(search, category)),};};导出默认连接(mapStateToProps,mapDispatchToProps)(搜索);

解决方案

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

>

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

const debounce = (fn, delay) =>{让 timerId;返回 (...args) =>{clearTimeout(timerId);timerId = setTimeout(() => fn(...args), delay);}};

示例使用:

fetchData = debounce(() => fetch(.....).then(..), 500);componentDidUpdate(.......) {//输入值不同,调用fetchData}<输入工具栏elementType={this.props.inputC.elementType}elementConfig={this.props.inputC.elementConfig}值={this.props.inputC.value}改变={this.props.onChangedHandler}/>

演示代码

const debounce = (fn, delay) =>{让 timerId;返回 (...args) =>{clearTimeout(timerId);timerId = setTimeout(fn, delay, [...args]);};};const fetch = (url, options) =>{控制台日志(获取",网址);返回新的承诺((解决)=> {setTimeout(() => {console.log(获取已解决");解决(`响应 - ${Math.floor(Math.random()* 1000)}`);}, 2000);});};导出默认类 App 扩展组件 {状态 = {搜索:",回复:"};changeHandler = (e) =>{const { value } = e.target;控制台日志(搜索",值);this.setState({ search: value });};fetchData = debounce(() => {const { 搜索 } = this.state;const 查询 = search.length ?`?orderBy="country"&equalTo="${search}"`:";拿来(https://react-hooks-update.firebaseio.com/ingredients.json"+ 查询).then((response) => this.setState({ response }));}, 500);componentDidUpdate(prevProps, prevState) {if (prevState.search !== this.state.search) {如果(this.state.response){this.setState({ 响应:"});}this.fetchData();}}使成为() {const { 响应,搜索 } = this.state;返回 (

<h1>Hello CodeSandbox</h1><h2>开始编辑,看看神奇的事情发生了!</h2><标签>搜索<输入类型=文本"value={search} onChange={this.changeHandler}/><div>去抖动响应:{response}</div>

);}}

I'm building a search field that is fetching from a data base upon users input and I'm struggling a bit. At the moment, it is fetching data in every keystroke, which is not ideal. I have looked at different answers and it seems that the best option is to do this in componentDidUpdate() and get a ref of the input feel to compare this with the current value through a setTimeout().

I have tried this, but I'm still fetching during every keystroke, not sure why? See a sample of the component below:


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);
        }
    }

I'm using Redux for state management. Here is the function that is called when fetching items:

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));
            });
    };
};

This is how I'm setting the ref in the search component:

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)}
            />
        );
    }
}

Based on a tutorial I found this should work. For your reference, see the code from this tutorial. I don't see why wouldn't the above work. The only difference is that the tutorial uses hooks.

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]);

Following recommendation to 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);

Here is the final solution after help here:

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);

解决方案

You really just need to debounce your input's onChange handler, or better, the function that is actually doing the asynchronous work.

Very simple debouncing higher order function:

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

Example Use:

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}
/>

Demo Code

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>
    );
  }
}

这篇关于当用户停止输入搜索框时执行 api 请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆