由于setState是异步的,因此按钮onClick可以在被禁用之前触发两次吗?

问题描述 投票:4回答:6

在下面的示例中,我们有一个启动和上传的按钮,并在上载过程中自行禁用。上传完成后,它会重新启用。

由于React的setState的异步性质,对于快速点击的用户,是否有可能在禁用按钮之前触发两次onClick回调?

请不要回答有关如何避免这种情况的解决方案,我想知道这种情况是否可能在第一时间,如果可能,如何重现它。

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  constructor (props) {
    super(props)

    this.state = {
      uploadDisabled: false
    }
  }


  upload = () => {
    this.setState({
      uploadDisabled: true
    })

    fetch('/upload').then(() => {
      this.setState({
        uploadDisabled: false
      })
    })
  }

  render () {
    return (
      <div>
        <button disabled={this.state.uploadDisabled} onClick={this.upload}>
          Upload
        </button>
      </div>
    )
  }
}
javascript reactjs
6个回答
1
投票

我不确定这是可能的:

class Button extends React.Component {
  constructor(){
    super();
    this.state = {disabled: false};
    this.onClick = () => {
      this.setState({
        disabled: true
      });
      console.log('click')

      setTimeout(() => {
        this.setState({
          disabled: false
        });
      }, 1000)
    };
  }

  render() {
    return <button onClick={this.onClick} disabled={this.state.disabled}>foo</button>;
  }
}

ReactDOM.render(
  <Button/>,
  document.getElementById('container')
);

setTimeout(() => {
  const button = document.getElementsByTagName('button')[0]
  for(let i = 0; i < 2; i++) {
    button.click()
  }
}, 200)

https://jsbin.com/tamifaruqu/edit?html,js,console,output

它只打印一次click


1
投票

我一直在尝试,并想知道我的解决方案是否可以解决问题。我在组件上定义了一个布尔属性,该属性不在状态上,并且在按钮单击时直接切换。这是代码:

class App extends React.Component {
  // not on the state, will be toggled directly when the user presses the button
  uploadInProgress = false;    
  // just for the purpose of experiment, has nothing to do with the solution
  uploadCalledCount = 0;    
  state = {
     uploadDisabled: false
  }
  upload = () => {
    if (this.uploadInProgress) return;

    this.uploadInProgress = true;
    this.uploadCalledCount++;

    // let's experiment and make setState be called after 1 second
    // to emulate some delay for the purpose of experiment
    setTimeout(() => {
      this.setState({ uploadDisabled: true })
    }, 1000);

    // emulate a long api call, for the purpose of experiment
    setTimeout(() => {
      this.setState(
          { uploadDisabled: false }, 
          () => { this.uploadInProgress = false; })
    }, 3000);
  }

  render() {
    return (
      <div>
        <button disabled={this.state.uploadDisabled} onClick={this.upload}>
          Upload
        </button>
        <br />
        {this.uploadCalledCount}
      </div>)
    }
}

这是一个工作示例om codesandbox

检查的方法:单击按钮的次数与焦虑和不耐烦的用户一样多,按钮将在setState调用之前设置一秒延迟后被禁用,然后upload函数的实际调用次数将出现在屏幕上(在state更改后),然后按钮在再次延迟3秒后启用。


0
投票
    import debounce from "lodash/debounce"
//import debounce at the top
    upload = () => {
        this.setState({
          uploadDisabled: true
        })

        fetch('/upload').then(() => {
          this.setState({
            uploadDisabled: false
          })
        })
      }
    onClickUpload = () => {
      _debounce(upload, time) // time is the number of milliseconds to delay.
    }
   render () {
    return (
      <div>
        <button disabled={this.state.uploadDisabled} onClick={this.onClickUpload}>
          Upload
        </button>
      </div>
    )
  }

这可能有所帮助


0
投票

最简单的方法是使用_.debounce(func, [wait=0], [options={}]) lodash这个函数忽略“等待”时间段的事件触发器


0
投票

这只是一个想法,但也许你可以使用onMouseDown和onMouseUp而不仅仅是onClick。 React的事件系统也提供了一个onDoubleClick事件,所以也许你可以尝试拦截它。

你可以在这里找到更多关于React事件的信息:https://reactjs.org/docs/events.html#mouse-events


0
投票

您可以为该按钮创建一个ref,并使用其ref来启用或禁用它。

<button ref="btn" onClick={this.onClick}>Send</button>

在onclick中,您可以禁用它

this.refs.btn.setAttribute("disabled", "disabled");

并启用,做

this.refs.btn.removeAttribute("disabled");

参考link

© www.soinside.com 2019 - 2024. All rights reserved.