Iulius Curt
Iulius Curt

Reputation: 5114

ReactJs: Prevent multiple times button press

In my React component I have a button meant to send some data over AJAX when clicked. I need to happen only the first time, i.e. to disable the button after its first use.

How I'm trying to do this:

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

});

What I think happens is the state variable showUploadButton not being updated right away, which the React docs says is expected.

How could I enforce the button to get disabled or go away altogether the instant it's being clicked?

Upvotes: 95

Views: 187316

Answers (15)

Vladimir Salguero
Vladimir Salguero

Reputation: 5947

Try with this code

<button 
    onClick={async (e) => {
    e.currentTarget.disabled = true;
    await onClickUploadFile();
    e.currentTarget.disabled = false;
}}>
    Upload
</button>

Upvotes: 0

Bayram
Bayram

Reputation: 515

If still someone facing with such issue, the solution is much easier then creating custom components etc...

Just,

  1. set a state value called disabled by default false.
  2. assign it to your button's disabled property.
  3. in onPress firstly set it to true, then do your stuff....
  4. then set it to false on your component's un mount...
function MyClickable() {
    const [disabled,setDisabled] = useState(false)
useEffect(() => {
return () => setDisabled(false)
},[])

    const onPress  = useCallback(() => {
                         setDisabled(true);
                         // do your stuff
                    },[]);

     <TouchableOpacity disabled={disabled} onPress={onPress}>
          // your things
     </TouchableOpacity>

Upvotes: 1

Kourosh Neyestani
Kourosh Neyestani

Reputation: 841

Try with this code:

class Form extends React.Component {
    constructor() {
        this.state = {
            disabled: false,
        };
    }

    handleClick() {
        this.setState({
            disabled: true,
        });

        if (this.state.disabled) {
            return;
        }

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

    render() {
        return (
            <button type="submit" onClick={() => this.handleClick()} disabled={this.state.disabled}>
                Submit
            </button>
        );
    }
}

ReactDOM.render(<Form />, document.getElementById('root'));

Upvotes: 0

Semih Can Bilgen
Semih Can Bilgen

Reputation: 414

My approach is if event on processing do not execute anything.

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

this.state = {
  onProcess:false
   }
}

uploadFile() {
 if (!this.state.onProcess){
   this.setState({
     onProcess: true
   });
   // then do your thing
   this.setState({
     onProcess: false;
   });
 }    
}

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

ReactDOM.render(<UploadArea />, document.body);

Upvotes: 0

Ngomane
Ngomane

Reputation: 1

Another approach could be like so:

<button onClick={this.handleClick} disabled={isLoading ? "disabled" :""}>Send</button>

Upvotes: 0

Hannes Schneidermayer
Hannes Schneidermayer

Reputation: 5747

Keep it simple and inline:

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

But! This will also disable the button, when the form calidation failed! So you will not be able to re-submit.

In this case a setter is better.

This fix this set the disabled in the onSubmit of the form:


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

And the button would look like this:

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

};

Upvotes: 1

saeta
saeta

Reputation: 4238

You can try using React Hooks to set the 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;

Make sure you are using ^16.7.0-alpha.x version or later of react and react-dom.

Hope this helps you!

Upvotes: 9

eltonkamami
eltonkamami

Reputation: 5190

What you could do is make the button disabled after is clicked and leave it in the page (not clickable element).

To achieve this you have to add a ref to the button element

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

and then on the onClickUploadFile function disable the button

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

You can then style the disabled button accordingly to give some feedback to the user with

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

If needed make sure to reenable it with

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

Update: the preferred way of handling refs in React is with a function and not a string.

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


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

Update: Using 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>

here is a small example using the code you provided https://jsfiddle.net/69z2wepo/30824/

Upvotes: 65

rudresh solanki
rudresh solanki

Reputation: 1015

You can get the element reference in the onClick callback and setAttribute from there, eg:

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

Upvotes: 1

Gazowski
Gazowski

Reputation: 66

By using event.target , you can disabled the clicked button. Use arrow function when you create and call the function onClick. Don't forget to pass the event in parameter.

See my codePen

Here is the code:

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

Upvotes: 2

Vajk Hermecz
Vajk Hermecz

Reputation: 5702

If you disable the button during onClick, you basically get this. A clean way of doing this would be:

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

See it in action here: https://codesandbox.io/s/extended-button-that-disabled-itself-during-onclick-execution-mg6z8

We basically extend the Button component with the extra behaviour of being disabled during onClick execution. Steps to do this:

  1. Create local state to capture if we are executing
  2. Extract properties we tamper with (disabled, onClick)
  3. Extend onClick operation with setting the execution state
  4. Render the button with our overridden onClick, and extended disabled

NOTE: You should ensure that the original onClick operation is async aka it is returning a Promise.

Upvotes: 7

Ashok R
Ashok R

Reputation: 20766

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

output

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

Upvotes: 1

junho
junho

Reputation: 3841

If you want, just prevent to submit.

How about using lodash.js debounce

Grouping a sudden burst of events (like keystrokes) into a single one.

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

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

Upvotes: 7

cquezel
cquezel

Reputation: 4487

The solution is to check the state immediately upon entry to the handler. React guarantees that setState inside interactive events (such as click) is flushed at browser event boundary. Ref: 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>

Upvotes: 84

Samuli Hakoniemi
Samuli Hakoniemi

Reputation: 19049

Tested as working one: 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);

Upvotes: 22

Related Questions