ReactJs:防止多次按下按钮

IT技术 javascript reactjs
2021-01-24 07:10:35

在我的 React 组件中,我有一个按钮,用于在单击时通过 AJAX 发送一些数据。我只需要第一次发生,即在第一次使用后禁用按钮。

我是如何尝试这样做的:

var UploadArea = React.createClass({

  getInitialState() {
    return {
      showUploadButton: true
    };
  },

  disableUploadButton(callback) {
    this.setState({ showUploadButton: false }, callback);
  },

  // This was simpler before I started trying everything I could think of
  onClickUploadFile() {
    if (!this.state.showUploadButton) {
      return;
    }
    this.disableUploadButton(function() {
      $.ajax({
        [...]
      });

    });
  },

  render() {
    var uploadButton;
    if (this.state.showUploadButton) {
      uploadButton = (
        <button onClick={this.onClickUploadFile}>Send</button>
      );
    }

    return (
      <div>
        {uploadButton}
      </div>
    );
  }

});

我认为发生的是状态变量showUploadButton没有立即更新,React 文档说这是预期的。

我怎么能强制按钮在被点击的瞬间被禁用或完全消失?

6个回答

解决方案是在进入处理程序时立即检查状态。React 保证交互事件(例如单击)内的 setState 在浏览器事件边界刷新。参考:https : //github.com/facebook/react/issues/11171#issuecomment-357945371

// In constructor
this.state = {
    disabled : false
};


// Handler for on click
handleClick = (event) => {
    if (this.state.disabled) {
        return;
    }
    this.setState({disabled: true});
    // Send     
}

// In render
<button onClick={this.handleClick} disabled={this.state.disabled} ...>
    {this.state.disabled ? 'Sending...' : 'Send'}
<button>
这是最干净的方法,应该是公认的答案。
2021-03-16 07:10:35
@cquezel 我知道每个按钮都有自己的处理程序,但this.state.disabled所有按钮都一样!不是吗?这就是为什么当我单击其中一个按钮时它禁用了我的所有按钮。我只想禁用我单击的那个按钮。
2021-03-30 07:10:35
@ZeeshanAhmadKhalil“this.state”对于每个按钮都是不同的。这就是“这个”的全部意义所在。“this”代表每个单独对象的状态。
2021-03-30 07:10:35
@cquezel 您的答案非常适合单个按钮。
2021-04-01 07:10:35
我也相信与@RBT 一样,这是最干净的方法,我们也在项目中以相同的方式这样做。:)
2021-04-12 07:10:35

您可以做的是在单击后禁用按钮并将其保留在页面中(不可单击元素)。

为此,您必须向按钮元素添加一个引用

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

然后在 onClickUploadFile 函数上禁用按钮

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

然后,您可以相应地设置禁用按钮的样式,以向用户提供一些反馈

.btn:disabled{ /* styles go here */}

如果需要,请确保重新启用它

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

更新:在 React 中处理 refs 的首选方式是使用函数而不是字符串。

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


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

更新:使用react-hooks

import {useRef} from 'react';
let btnRef = useRef();

const onBtnClick = e => {
  if(btnRef.current){
    btnRef.current.setAttribute("disabled", "disabled");
  }
}

<button ref={btnRef} onClick={onBtnClick}>Send</button>

这是一个使用您提供的代码的小例子 https://jsfiddle.net/69z2wepo/30824/

@KushalKumar 如何为这个问题提供适当的去抖动解决方案,以及对于仅一次的情况来说什么速率是足够的?
2021-03-18 07:10:35
这让我走到了一半,但 React 团队不赞成给 ref 一个字符串值,而是使用它一个回调:reactjs.org/docs/refs-and-the-dom.html
2021-03-30 07:10:35
最好的分析器是debounce
2021-04-03 07:10:35
它给了我一个错误“TypeError: self.btn.setAttribute is not a function”:(
2021-04-05 07:10:35
@KushalKumar 我的观点是这与速度无关。要求是“按钮只能单击一次”。这就是为什么我认为 debouce 不是这项工作的正确工具。
2021-04-14 07:10:35

测试为工作之一:http : //codepen.io/zvona/pen/KVbVPQ

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

    this.state = {
      isButtonDisabled: false
    }
  }

  uploadFile() {
    // first set the isButtonDisabled to true
    this.setState({
      isButtonDisabled: true
    });
    // then do your thing
  }

  render() {
    return (
      <button
        type='submit'
        onClick={() => this.uploadFile()}
        disabled={this.state.isButtonDisabled}>
        Upload
      </button>
    )
  }
}

ReactDOM.render(<UploadArea />, document.body);
@Awol 提出了一个很好的观点, this.setState() 的批处理会导致双击仍然发生。
2021-03-31 07:10:35
根据组件的复杂性,它应该足够快以进行双击,并且它比使用 refs 添加属性要好得多。
2021-04-06 07:10:35
@Awol React 保证在浏览器事件边界刷新交互事件(例如单击)中的 setState。请参阅下面的我的答案。如果您在事件处理程序中更改读取或设置状态,这将不是问题。
2021-04-07 07:10:35
@cquezel,我不知道这个。今天学了点儿新东西。好找,谢谢!
2021-04-09 07:10:35
这不会解决问题,因为 React 会消除状态更新的抖动。因此,this.state.isButtonDisabled获取“假”值总是会有延迟快速连续单击两次仍会注册 2 个 onClick 事件。
2021-04-13 07:10:35

您可以尝试使用React Hooks来设置Component State

import React, { useState } from 'react';

const Button = () => {
  const [double, setDouble] = useState(false);
  return (
    <button
      disabled={double}
      onClick={() => {
        // doSomething();
        setDouble(true);
      }}
    />
  );
};

export default Button;

确保您使用的^16.7.0-alpha.xreact和 的版本或更高版本react-dom

希望这对你有帮助!

你的意思是 useState
2021-03-17 07:10:35
这个按钮将永远保持禁用状态,或者直到页面刷新或组件呈现?这似乎不太理想?
2021-03-31 07:10:35
这不会立即禁用按钮。您依靠 setter 来生效,这需要重新绘制组件,因此它不会立即生效。
2021-04-12 07:10:35

如果您在 onClick 期间禁用按钮,您基本上会得到这个。一个干净的方法是:

import React, { useState } from 'react';
import Button from '@material-ui/core/Button';

export default function CalmButton(props) {
    const [executing, setExecuting] = useState(false);

    const {
        disabled,
        onClick,
        ...otherProps
    } = props;

    const onRealClick = async (event) => {
        setExecuting(true);
        try {
            await onClick();
        } finally {
            setExecuting(false);
        }
    };

    return (
        <Button
            onClick={onRealClick}
            disabled={executing || disabled}
            {...otherProps}
        />
    )
}

在这里查看它的实际效果:https : //codesandbox.io/s/extended-button-that-disabled-itself-during-onclick-execution-mg6z8

我们基本上使用在 onClick 执行期间被禁用的额外行为来扩展 Button 组件。执行此操作的步骤:

  1. 如果我们正在执行,则创建本地状态以捕获
  2. 提取我们篡改的属性(禁用,onClick)
  3. 通过设置执行状态扩展 onClick 操作
  4. 使用我们覆盖的 onClick 渲染按钮,并禁用扩展

注意:您应该确保原始的 onClick 操作是异步的,也就是返回一个 Promise。

为什么最小值是 600/1000 毫秒?如果运行时间更短会怎样?
2021-03-22 07:10:35
如果小于 600/1000 毫秒,则 someOperation()(在您的示例中)双击运行两次。但这完全正常,因为之前会检测到第二次点击。如果我在您的示例中更改 'await sleep(1000);',则可以轻松重现这一点 '等待睡眠(10);'
2021-04-01 07:10:35
这如何防止按钮仅被单击一次?
2021-04-07 07:10:35
这是一种非常干净的方法。但一件重要的事情:异步任务持续时间需要高于 600/1000 毫秒!!!为了确保它一直有效,在“await onClick();”之后添加“await sleep(1000)” . 睡眠记录在原始示例中
2021-04-10 07:10:35
但是再次 ClamButton 很好,我将它添加到我的工具集中:)
2021-04-11 07:10:35