【问题标题】:ReactJs: Prevent multiple times button pressReactJs:防止多次按下按钮
【发布时间】:2016-05-20 20:15:55
【问题描述】:

在我的 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 文档说这是意料之中的。

我怎样才能强制按钮在被点击的那一刻被禁用或完全消失?

【问题讨论】:

  • 您是否注意到问题或只是想知道?你能双份提交吗?
  • Formik库默认处理这个问题,在Formik官网搜索issubmitting
  • 请查看@cquezel 的答案以获得更简洁的方法。使用 ref 禁用表单控件(显示在接受的答案中)是一件老事,可能与 ReactJs 的初始版本有关。

标签: javascript reactjs


【解决方案1】:

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

要实现这一点,您必须向按钮元素添加 ref

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

更新:使用反应钩子

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/

【讨论】:

  • 这让我成功了一半,但 React 团队不赞成给 ref 一个字符串值,而是使用回调:reactjs.org/docs/refs-and-the-dom.html
  • 它给了我一个错误'TypeError: self.btn.setAttribute is not a function' :(
  • 最好的分析器是debounce
  • @KushalKumar debounce 如何是解决此问题的适当解决方案?对于仅一次的情况,什么速率是适当的?
  • @KushalKumar 我的意思是,这与速度无关。要求是“按钮只能被点击一次”。这就是为什么我不认为 debouce 是适合这项工作的工具。
【解决方案2】:

测试为工作之一: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);

【讨论】:

  • 这不会解决问题,因为状态更新会被 React 反跳。正因为如此,this.state.isButtonDisabled 总是会延迟获得“假”值。快速连续单击两次仍会注册 2 个 onClick 事件。
  • @Awol 提出了一个很好的观点,this.setState() 的批处理会导致双击仍然发生。
  • 根据组件的复杂性,它应该足够快,可以双击,并且比使用 refs 添加属性要好得多。
  • @Awol React 保证交互事件(例如点击)中的 setState 在浏览器事件边界处刷新。请参阅下面的答案。如果您在事件处理程序中更改读取或设置状态,这将不是问题。
  • @cquezel,我不知道这个。今天学了点儿新东西。很好的发现,谢谢!
【解决方案3】:

解决方案是在进入处理程序后立即检查状态。 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>

【讨论】:

  • 这是最干净的方法,应该是公认的答案。
  • 我也相信和@RBT一样,这是最干净的方式,我们也在项目中这样做。 :)
  • @cquezel 我知道每个按钮都有自己的处理程序,但 this.state.disabled 对于所有按钮都是一样的!不是吗?这就是为什么当我单击其中一个按钮时它禁用了我所有的按钮。我只想禁用我点击的那个按钮。
  • @cquezel 您的答案非常适合单个按钮。
  • @ZeeshanAhmadKhalil "this.state" 对于每个按钮都不同。这就是“这个”的全部意义所在。 “this”表示每个单独对象的状态。
【解决方案4】:

如果您愿意,请阻止提交。

lodash.js debounce怎么样

将突发事件(如击键)组合成一个事件。

https://lodash.com/docs/4.17.11#debounce

<Button accessible={true}
    onPress={_.debounce(async () => {
                await this.props._selectUserTickets(this.props._accountId)
    }, 1000)}
></Button>

【讨论】:

    【解决方案5】:

    您可以尝试使用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.x 或更高版本的reactreact-dom

    希望对你有帮助!

    【讨论】:

    • 你的意思是useState
    • 这个按钮会永远保持禁用状态,或者直到页面被刷新或组件被渲染?这似乎不太理想?
    • 这不会立即禁用按钮。您依赖于 setter 来生效,这需要重新绘制组件,因此它不会是即时的。
    【解决方案6】:
    const once = (f, g) => {
        let done = false;
        return (...args) => {
            if (!done) {
                done = true;
                f(...args);
            } else {
                g(...args);
            }
        };
    };
    
    const exampleMethod = () => console.log("exampleMethod executed for the first time");
    const errorMethod = () => console.log("exampleMethod can be executed only once")
    
    let onlyOnce = once(exampleMethod, errorMethod);
    onlyOnce();
    onlyOnce();
    

    输出

    exampleMethod executed for the first time
    exampleMethod can be executed only once
    

    【讨论】:

      【解决方案7】:

      如果你在 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

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

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

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

      【讨论】:

      • 这是一个非常干净的方法。但一件重要的事情:异步任务的持续时间需要高于 600/1000 毫秒!!!为了确保它一直有效,在“await onClick();”之后添加“await sleep(1000)” . sleep 记录在原始示例中
      • 为什么 600/1000ms 最小?如果运行时间更短会怎样?
      • 如果小于 600/1000 毫秒,则 someOperation()(在您的示例中)双击运行两次。但这完全正常,因为之前会检测到第二次点击。如果我在您的示例中更改“等待睡眠(1000);”,这可以很容易地复制。 '等待睡眠(10);'
      • 但是 ClamButton 还是不错的,我在我的工具集中添加了它:)
      • 如何防止按钮被点击一次?
      【解决方案8】:

      通过使用 event.target ,您可以禁用单击的按钮。 在创建和调用函数onClick 时使用箭头函数。不要忘记在参数中传递事件。

      见我的codePen

      代码如下:

      class Buttons extends React.Component{
        constructor(props){
          super(props)
          this.buttons = ['A','B','C','D']
        }
      
        disableOnclick = (e) =>{
          e.target.disabled = true
        }
      
        render(){
          return(
      
           <div>
              {this.buttons.map((btn,index) => (
                <button type='button' 
                  key={index} 
                  onClick={(e)=>this.disableOnclick(e)}
                  >{btn}</button>
              ))}
            </div>
        )}
      
      }
      ReactDOM.render(<Buttons />, document.body);
      

      【讨论】:

        【解决方案9】:

        您可以在 onClick 回调和setAttribute 中获取元素引用,例如:

              <Button
                onClick={(e) => {
                  e.target.setAttribute("disabled", true);
                  this.handler();
                }}            
              >
                Submit
              </Button>
        

        【讨论】:

        • 这似乎不是 Reacty 的方法。让我想起了老式的 JS 开发。
        【解决方案10】:

        保持简单和内联:

        <button type="submit"
                onClick={event => event.currentTarget.disabled = true}>
            save
        </button>
        

        但是!当表单校准失败时,这也会禁用按钮!所以你将无法重新提交。

        在这种情况下,setter 更好。

        此修复此设置在表单的onSubmit 中禁用:

        
        // state variable if the form is currently submitting
        const [submitting, setSubmitting] = useState(false);
        
        // ...
        return (
        <form onSubmit={e => {
                        setSubmitting(true); // create a method to modify the element
                    }}>
        
            <SubmitButton showLoading={submitting}>save</SubmitButton>
        </form>
        );
        

        按钮看起来像这样:

        import {ReactComponent as IconCog} from '../../img/icon/cog.svg';
        import {useEffect, useRef} from "react";
        
        export const SubmitButton = ({children, showLoading}) => {
        
            const submitButton = useRef();
        
            useEffect(() => {
                if (showLoading) {
                    submitButton.current.disabled = true;
                } else {
                    submitButton.current.removeAttribute("disabled");
                }
            }, [showLoading]);
        
            return (
                <button type="submit"
                        ref={submitButton}>
                    <main>
                        <span>{children}</span>
                    </main>
                </button>
            );
        
        };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-05-18
          • 1970-01-01
          • 2014-12-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-11
          相关资源
          最近更新 更多