velkoon
velkoon

Reputation: 962

React - How to fix very inconsistent disabled button countdown (i.e., several whole seconds)

I'm using setTimeout() (I even tried driftless too) to change buttons from disabled = true to disabled = false 3 seconds after appearing on the DOM (onLoad=). The buttons appear in sequence, after the previous button has been clicked.

The problem is, the setTimeout() goes absolutely haywire: Each button's disabled = true duration is seemingly sporadic (at least on Windows Chrome on 2 different PC's). Sometimes the button seemingly waits the full 3 seconds to enable, sometimes it enables instantaneously, and everything in-between.

What is causing this inconsistency, and how can I get each button to enable exactly 3 seconds (well, within a couple hundred milliseconds...not a big deal) after appearing?

CodePen

Javascript

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

export default class MyForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = { is_button_disabled: true, show_button_1: true, show_button_2: false, show_button_3: false, show_button_4: false };
  }
  handleDisableButton = (event) => {
    setTimeout(() => this.setState({ is_button_disabled: false }), 3000);
  }
  handleShowButton = (buttonId) => {
    if (buttonId === 'button_1'){
      this.setState({ is_button_disabled: true})
      this.setState({ show_button_1: false, show_button_2: true})
    }
    if (buttonId === 'button_2'){
      this.setState({ is_button_disabled: true})
      this.setState({ show_button_2: false, show_button_3: true})
    }
    if (buttonId === 'button_3'){
      this.setState({ is_button_disabled: true})
      this.setState({ show_button_3: false, show_button_4: true})
    }
    if (buttonId === 'button_4'){
      this.setState({ is_button_disabled: true})
      this.setState({ show_button_4: false, show_button_1: true})
    }
  }
  render() {
    const { show_button_1, show_button_2, show_button_3, show_button_4 } = this.state;
    return (
      <div butttons>
        {show_button_1 &&
          <button onLoad={this.handleDisableButton()} onClick={() => this.handleShowButton('button_1')} disabled={this.state.is_button_disabled}>Button1</button>
        }
        {show_button_2 &&
          <button onLoad={this.handleDisableButton()} onClick={() => this.handleShowButton('button_2')} disabled={this.state.is_button_disabled}>Button2</button>
        }
        {show_button_3 &&
          <button onLoad={this.handleDisableButton()} onClick={() => this.handleShowButton('button_3')} disabled={this.state.is_button_disabled}>Button3</button>
        }
        {show_button_4 &&
          <button onLoad={this.handleDisableButton()} onClick={() => this.handleShowButton('button_4')} disabled={this.state.is_button_disabled}>Button4</button>
        }
      </div>
    )
  }
}

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

HTML

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React App</title>
</head>

<body>
    <noscript>
        You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
</body>

</html>

Upvotes: 1

Views: 124

Answers (3)

Zhang TianYu
Zhang TianYu

Reputation: 1195

You should fix this first.

<button id="myButton" onLoad={this.handleDisableButton} onClick={() => this.handleShowButton('button_1')} disabled={this.state.is_button_disabled}>Button1</button>

which means

this.handleDisableButton()

to

this.handleDisableButton

And clearTimeout to prevent memory leak

I have fixed your sandboxcode:

  componentDidMount() {
    this.timeoutHandler = setTimeout(() => {
      clearTimeout(this.timeoutHandler);
      this.setState({ is_button_disabled: false });
    }, 3000);
  }
  handleShowButton = (buttonId) => {
    let newState = {};
    if (buttonId === "button_1") {
      newState = {
        is_button_disabled: true,
        show_button_1: false,
        show_button_2: true
      };
    }
    if (buttonId === "button_2") {
      newState = {
        is_button_disabled: true,
        show_button_2: false,
        show_button_3: true
      };
    }
    if (buttonId === "button_3") {
      newState = {
        is_button_disabled: true,
        show_button_3: false,
        show_button_4: true
      };
    }
    if (buttonId === "button_4") {
      newState = {
        is_button_disabled: true,
        show_button_4: false,
        show_button_1: true
      };
    }
    this.setState(newState, () => {
      this.timeoutHandler = setTimeout(() => {
        clearTimeout(this.timeoutHandler);
        this.setState({ is_button_disabled: false });
      }, 3000);
    });
  };
  componentWillUnmount() {
    clearTimeout(this.timeoutHandler);
  }

Upvotes: 3

Steve Bunting
Steve Bunting

Reputation: 439

There is no onLoad event for buttons, which is why the method is never getting called if you pass it in by reference and only gets called when invoked directly.

You should call handleDisableButton() as a callback to setState() in your onClick method, ie:

if (buttonId === "button_1") {
  this.setState({
    is_button_disabled: true,
    show_button_1: false,
    show_button_2: true
  }, () => {
    handleDisableButton();
  );
}

This ensures that the timeout will start only once the state has been updated, and the timeout callback should consistently be called after 3 seconds.

Upvotes: 0

icebug
icebug

Reputation: 303

While using setState, can you try this.setState({ ...this.state, is_button_disabled: true}) this.setState({ ...this.state, show_button_4: false, show_button_1: true})

Upvotes: 0

Related Questions