【问题标题】:How to start search only when user stops typing?仅当用户停止输入时如何开始搜索?
【发布时间】:2017-07-02 05:02:05
【问题描述】:

当用户停止输入时我需要执行搜索。我知道我应该使用 setTimeout() 。但是对于 Reactjs,我找不到它是如何工作的。 有人可以告诉我当用户停止输入几秒钟(假设 5)时如何调用一个方法(将处理搜索)。我不知道找出在哪里编写代码以检查用户是否已停止输入。

import React, {Component, PropTypes} from 'react';

export default class SearchBox extends Component {

    state={
      name:" ",
    }

    changeName = (event) => {
        this.setState({name: event.target.value}); 
    }

    sendToParent = () => {
        this.props.searching(this.state.name);
    }

    render() {
        return (
            <div>
                 <input type="text"  placeholder='Enter name you wish to Search.'  onChange={this.changeName} />

            </div>
        );
    }
}   

我想在用户停止输入时调用 sendToParent 方法。

【问题讨论】:

  • 他们是在输入输入元素吗?如果是这样,输入元素有一个名为“onKeyPress”的属性,在选择该输入时,每次按下按钮时都会调用该属性。所以你可以让它在每次他们按下按钮时开始超时,但如果他们在超时执行之前再次按下按钮,它会重置计时器。如果他们没有输入 X 次,则超时执行搜索。
  • 如果您指的是执行超时搜索和“停止输入”检查的实际代码,那么网上有很多示例
  • 我的方法没有调用 onkeyPress
  • 区分大小写,'onkeyPress' 不起作用。必须是“onKeyPress”

标签: reactjs settimeout


【解决方案1】:

您可以对您的代码使用setTimeout,如下所示,

state = {
    name: '',
    typing: false,
    typingTimeout: 0
}
changeName = (event) => {
    const self = this;

    if (self.state.typingTimeout) {
       clearTimeout(self.state.typingTimeout);
    }

    self.setState({
       name: event.target.value,
       typing: false,
       typingTimeout: setTimeout(function () {
           self.sendToParent(self.state.name);
         }, 5000)
    });
}

另外,你需要在构造函数中绑定changeName处理函数。

constructor(props) {
   super(props);
   this.changeName = this.changeName.bind(this);
}

【讨论】:

  • 我迟到了,但我想指出的是,使用 es6 箭头语法时,您不需要绑定 changeName 函数。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • 出于同样的原因,您不需要设置const self = thisthis 已绑定到类范围。
  • 有一个错字:``` state = { ... typingTimeOut: 0 } ``` typingTimeout 是用大写的 O 写的。无法编辑答案来修正错字。跨度>
  • 这里是否需要 typing 状态字段?
  • typing 字段看起来没有必要。当你使用箭头函数changeName时,你不需要在构造函数中绑定this
【解决方案2】:

使用 useEffect 钩子实现:

function Search() {
  const [searchTerm, setSearchTerm] = useState('')

  useEffect(() => {
    const delayDebounceFn = setTimeout(() => {
      console.log(searchTerm)
      // Send Axios request here
    }, 3000)

    return () => clearTimeout(delayDebounceFn)
  }, [searchTerm])

  return (
    <input
      autoFocus
      type='text'
      autoComplete='off'
      className='live-search-field'
      placeholder='Search here...'
      onChange={(e) => setSearchTerm(e.target.value)}
    />
  )
}

【讨论】:

  • 小心,这会重新渲染组件
  • 简单且具有魅力。此解决方案需要更多投票
  • 可惜@AljohnYamaro 被忽略了。他的观点很好
  • @AljohnYamaro,你能解释一下为什么它会被重新渲染吗?
【解决方案3】:

另一种对我有用的方法:

class Search extends Component {
  constructor(props){
    super(props);
    this.timeout =  0;
  }

  doSearch(evt){
    var searchText = evt.target.value; // this is the search text
    if(this.timeout) clearTimeout(this.timeout);
    this.timeout = setTimeout(() => {
      //search function
    }, 300);
  }

   render() {
    return (
      <div className="form-group has-feedback">
        <label className="control-label">Any text</label>
        <input ref="searchInput" type="text" onChange={evt => this.doSearch(evt)} />
      </div>
    );
  }
}

【讨论】:

  • 我得到了这个工作,但它仍然搜索第一个字母
  • 这工作正常,直到我将值作为输入字段的状态..
【解决方案4】:

我使用了lodash的去抖功能

onChangeSearchInput = (evt)=> {
    this.debouncedSearch(evt.target.value);
};

debouncedSearch = debounce(function (query) {
    this.setState({query});
}, 1000);

在我的渲染方法的某个地方我有这个输入字段

<input
    type='text'
    onChange={this.onChangeSearchInput}
    className='uk-input'
    placeholder={'search by name or email...'}
   />

【讨论】:

  • 调用一个全新的(巨大的)库来延迟一个函数是不好的,对吧?
  • @w3debugger 你是对的。但我的项目中已经有 lodash 用于其他用途。
  • 你也可以只导入一个特定的函数,而不是拉入整个库。
【解决方案5】:

这个library (use-debounce) 很简单。

设置

yarn add use-debounce

npm i use-debounce --save

文档中的使用示例

import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';

export default function Input() {
  const [text, setText] = useState('Hello');
  const [value] = useDebounce(text, 1000);

  return (
    <div>
      <input
        defaultValue={'Hello'}
        onChange={(e) => {
          setText(e.target.value);
        }}
      />
      <p>Actual value: {text}</p>
      <p>Debounce value: {value}</p>
    </div>
  );
}

我现在喜欢的东西,可能会有所不同 未来!:

  • 易于设置和使用
  • 更少的样板代码
  • 中等评分 (~1K) 和使用情况(npm - 200K 下载/周)
  • 支持超时、MaxWait 等功能

【讨论】:

  • 这应该是公认的答案!这个答案为我节省了很多时间(和代码)
【解决方案6】:

我认为我们可以用一种更简单、更干净的方式来做这件事,而不会破坏调用完整组件生命周期的 state 参数,如下所示:

constructor(props) {
    super(props);

    //Timer
    this.typingTimeout = null;

    //Event
    this.onFieldChange = this.onFieldChange.bind(this);

    //State
    this.state = { searchValue: '' }; 
}   


 /**
 * Called on the change of the textbox.
 * @param  {[Object]} event [Event object.]
 */
onFieldChange(event) {
    // Clears the previously set timer.
    clearTimeout(this.typingTimeout);

    // Reset the timer, to make the http call after 475MS (this.callSearch is a method which will call the search API. Don't forget to bind it in constructor.)
    this.typingTimeout = setTimeout(this.callSearch, 475);

    // Setting value of the search box to a state.
    this.setState({ [event.target.name]: event.target.value });
}


<div className="block-header">
     <input
           type="text"
           name="searchValue"
           value={this.state.searchValue}
           placeholder="User Name or Email"
           onChange={this.onFieldChange}
     />
</div>

【讨论】:

    【解决方案7】:

    自定义钩子怎么样?

    import {useEffect, useRef, useState} from "react";
    
    export default function useSearchInputState(searchHandler) {
      
      // to prevent calling the handler on component mount
      const didMountRef = useRef(false);
    
      const [searchValue, setSearchValue] = useState(null);
    
      useEffect(() => {
        let delayDebounceFn;
    
        if (didMountRef.current) {
          delayDebounceFn = setTimeout(searchHandler, 600)
        } else {
          didMountRef.current = true;
        }
    
        return () => clearTimeout(delayDebounceFn);
      }, [searchValue]); // eslint-disable-line react-hooks/exhaustive-deps
    
      return [searchValue, setSearchValue];
    
    }
    

    用法:

    function MyComponent(props) {
    
      const [searchValue, setSearchValue] = useSearchInputState(() => {
        resetData(searchValue ?? null, selectedFilterPos); // replace with your code
      });
    
      return (
        <input className="Search"
               onChange={e => setSearchValue(e?.target?.value ?? null)}
          />
      );
    }
    

    【讨论】:

      【解决方案8】:

      你可以只使用 lodash 的 debounce 或使用 setTimeout 进行模拟。

      import React, {Component, PropTypes} from 'react';
      
      export default class SearchBox extends Component {
          constructor(props){
             super(props);
             this.state={ name:" "}
             this.timeout =  null;
      
          }
      
          changeName = (event) => {
              clearTimeout(timeout);
               if(timeout){
                 setTimeout((event)=> this.setState({name: event.target.value}), 200)
               }
          }
      
          sendToParent = () => {
              this.props.searching(this.state.name);
          }
      
          render() {
              return (
                  <div>
                       <input type="text"  placeholder='Enter name you wish to Search.'  onChange={this.changeName} />
      
                  </div>
              );
          }
      }
      

      【讨论】:

      • 我认为目标将是 undefined ins setTImeout call
      【解决方案9】:

      我已经使用了这个自定义钩子,它的工作完全没有问题。

      export function useSearchDebounce(delay = 350) {
        const [search, setSearch] = useState(null);
        const [searchQuery, setSearchQuery] = useState(null);
      
        useEffect(() => {
          const delayFn = setTimeout(() => setSearch(searchQuery), delay);
          return () => clearTimeout(delayFn);
        }, [searchQuery, delay]);
      
        return [search, setSearchQuery];
      }
      

      在任何地方都可以使用

      const [search, setSearch] = useSearchDebounce();
      
      <input onChange={(e) => setSearch(e.target.value)}/>
      

      【讨论】:

        【解决方案10】:

        你可以使用 react hooks useEffect 和 setTimeOut 函数,因为它总是返回计时器 id,你可以很容易地清除具有该 id 的计时器,如下所示

        export const Search = () => {
        const [term, setTerm] = useState();
        const [results, setResult] = useState([]);
        
        useEffect(() => {
            const searchWiki = async () => {
                const { data } = await axios.get('https://en.wikipedia.org/w/api.php', {
                    params: {
                        srsearch: term,
                    },
                });
        
                setResult(data.query.search);
            };
            const timerId = setTimeout(() => {
                searchWiki();
             // make a request after 1 second since there's no typing 
            }, 1000);
        
            return () => {
                clearTimeout(timerId);
            };
        }, [term]);
        

        【讨论】:

        • 像魅力一样工作
        【解决方案11】:

        对于 React 钩子:

        首先我们要定义一个组件

        import React, { useEffect, useState } from "react";
        
        const SearchInputText = ({ value, name, placeholder, onChange }) => {
          // state for keepign search text 
          const [searchText, setSearchText] = useState(value);
          // state for keeping the timeout
          const [searchTextTimeout, setSearchTextTimeout] = useState(null);
        
          // handler for form submit (pressing enter without waiting for setimeout to trigger)
          const handleSubmit = (e) => {
            e.preventDefault();
            // clear timeout as it'll that would be triggered
            if (searchTextTimeout) {
              clearTimeout(searchTextTimeout);
            }
            onChange(searchText);
          };
        
          // onChange handler
          const handleOnChange = (e) => {
          // cancelling previous timeouts
            if (searchTextTimeout) {
              clearTimeout(searchTextTimeout);
            }
            // first update the input text as user type
            setSearchText(e.target.value);
            // initialize a setimeout by wrapping in our searchTextTimeout so that we can clear it out using clearTimeout
            setSearchTextTimeout(
              setTimeout(() => {
                onChange(searchText);
                // timeout is 2500ms, change it to less or more.
              }, 2500),
            );
          };
        
          // making sure that we clear the timeout if/when the component unmount
          useEffect(() => {
            return () => clearTimeout(searchTextTimeout);
          }, [searchTextTimeout]);
        
          return (
            <form onSubmit={handleSubmit}>
              <input
                name={name}
                placeholder={placeholder}
                type="text"
                value={searchText}
                onChange={handleOnChange}
              />
            </form>
          );
        };
        
        export default SearchInputText;
        
        

        用法:

        const Parent = () => {
          const handleChange = (e) => {
            // your implementation here
          };
          return (
            <div>
              <SortSearchInput name="search" placeholder="Enter Search" onChange={handleChange} />
            </div>
          );
        };
        

        【讨论】:

        • 也许您可以添加更多的 cmets 来教育提出问题的人。
        【解决方案12】:

        我像这样制作了自己的自定义组件。

        import React, { useState, useEffect } from 'react'
        
        const InputDebounce = props => {
          const { onChange, ...otherProps } = props
        
          const [inputTimeout, setInputTimeout] = useState(null)
        
          useEffect(() => () => clearTimeout(inputTimeout), [inputTimeout])
        
          const inputOnChange = value => {
            if (inputTimeout) clearTimeout(inputTimeout)
            setInputTimeout(
              setTimeout(() => {
                if (onChange) onChange(value)
              }, 1000)
            )
          }
        
          return (
            <input
              {...otherProps}
              onChange={e => inputOnChange(e.target.value)}
            />
          )
        }
        
        export default InputDebounce
        

        并且像这样使用任何地方。

        import React from 'react'
        import ReactDOM from 'react-dom'
        
        import InputDebounce from './InputDebounce'
        
        const App = () => {
          const usernameOnChange = value => {
            console.log(value)
          }
        
          return (
            <div>
              <InputDebounce
                type='text'
                name='username'
                placeholder='Username'
                onChange={usernameOnChange}
              />
            </div>
          )
        }
        
        ReactDOM.render(<App />, document.getElementById('root'))
        

        【讨论】:

          【解决方案13】:

          这是一个工作组件模板,其中包含一些有用的参数来帮助您入门。

          import React, { Component } from 'react'
          
          const initialState = { results: [], value: '' }
          
          export default class SearchBox extends Component {
            state = initialState
            timeout = null
            search_url = "https://example.com/search?q="
            min_query_length = 2
            timeout_duration = 300
          
            handleSearchChange = (e) => {
              let value = e.target.value
              clearTimeout(this.timeout);
              if (value.length < 1) {
                  return this.setState(initialState) 
              } else {
                  this.setState({ value })
                  if (value.length>=this.min_query_length) {    
                      this.timeout = setTimeout(this.search, this.timeout_duration);
                  }
              }
            }
          
            search = () => {
              // assuming your results are returned as JSON
              fetch(`${this.search_url}${this.state.value}`)
              .then(res => res.json())
              .then(data => {
                  this.setState({
                      results: data,
                  })
              })
            }
          
            render() {
              return (
                    <input
                      onChange={this.handleSearchChange}
                    />
              )
            }
          }
          
          

          【讨论】:

            【解决方案14】:

            使用根据@anoNewb 的回答修改的反应钩子。有补充:

            • 在计时器仍在运行时防止多个触发器
            • 添加表单提交事件

            codesandbox

                import React, { useState, useEffect } from "react";
            
                export default function App() {
                  const [search, setSearch] = useState("");
                  const [searchTimeout, setSearchTimeout] = useState(null);
            
                  useEffect(() => {
                    if (searchTimeout) {
                      clearTimeout(searchTimeout);
                    }
            
                    setSearchTimeout(
                      setTimeout(() => {
                        loadUsers();
                      }, 1000),
                    );
            
                    return () => clearTimeout(searchTimeout);
                  }, [search]);
            
                  const loadUsers = () => {
                    console.log("axios call with query: ", search);
                  };
            
                  return (
                    <div className="App">
                      <form
                        onSubmit={(e) => {
                          e.preventDefault();
                          if (searchTimeout) {
                            clearTimeout(searchTimeout);
                          }
                          loadUsers();
                        }}
                      >
                        <input
                          onChange={(e) => {
                            setSearch(e.target.value);
                          }}
                        />
                      </form>
                    </div>
                  );
                }
            

            【讨论】:

              【解决方案15】:

              用户lodash javascript 库并使用[_.debounce][1]

              changeName: _.debounce(function (val) {
                console.log(val)                
              }, 1000)
              

              【讨论】:

                【解决方案16】:

                Typeahead 库问题https://twitter.github.io/typeahead.js/

                由于这里的情况很简单,我可以使用一个快速而肮脏的解决方案:

                onChange: (event) ->
                  if @_timeoutTask?
                    clearTimeout @_timeoutTask
                
                  @_timeoutTask = setTimeout (=>
                    @sendToParent event.target.value
                    clearTimeout @_timeoutTask
                  ), 5000
                

                这样,任务会在输入事件5s后触发。如果有新的事件发生,旧的任务将被取消并安排一个新的任务,然后再等待5s。

                React 的不同之处在于存储计算状态的位置,例如 _timeoutTask。文件范围、组件状态或组件实例。

                由于_timeoutTask 是组件级别,因此应该全局存储。而且它不影响渲染,所以也不在组件状态。所以我建议直接将它附加到组件实例。

                【讨论】:

                  猜你喜欢
                  • 2011-02-02
                  • 1970-01-01
                  • 2021-05-29
                  • 1970-01-01
                  • 2020-07-22
                  • 2011-03-07
                  • 2021-11-15
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多