How to disable button in React.js
Asked Answered
P

9

164

I have this component:

import React from 'react';

export default class AddItem extends React.Component {

add() {
    this.props.onButtonClick(this.input.value);
    this.input.value = '';
}


render() {
    return (
        <div className="add-item">
            <input type="text" className="add-item__input" ref={(input) => this.input = input} placeholder={this.props.placeholder} />
            <button disabled={!this.input.value} className="add-item__button" onClick={this.add.bind(this)}>Add</button>
        </div>
    );
}

}

I want the button to be disabled when input value is empty. But the code above doesn't work. It says:

add-item.component.js:78 Uncaught TypeError: Cannot read property 'value' of undefined

pointing to disabled={!this.input.value}. What can I be doing wrong here? I'm guessing that perhaps ref isn't created yet when render method is executed. If, so what is the workararound?

Pearlstein answered 5/1, 2017 at 15:27 Comment(0)
H
217

Using refs is not best practice because it reads the DOM directly, it's better to use React's state instead. Also, your button doesn't change because the component is not re-rendered and stays in its initial state.

You can use setState together with an onChange event listener to render the component again every time the input field changes:

// Input field listens to change, updates React's state and re-renders the component.
<input onChange={e => this.setState({ value: e.target.value })} value={this.state.value} />

// Button is disabled when input state is empty.
<button disabled={!this.state.value} />

Here's a working example:

class AddItem extends React.Component {
  constructor() {
    super();
    this.state = { value: '' };
    this.onChange = this.onChange.bind(this);
    this.add = this.add.bind(this);
  }

  add() {
    this.props.onButtonClick(this.state.value);
    this.setState({ value: '' });
  }

  onChange(e) {
    this.setState({ value: e.target.value });
  }

  render() {
    return (
      <div className="add-item">
        <input
          type="text"
          className="add-item__input"
          value={this.state.value}
          onChange={this.onChange}
          placeholder={this.props.placeholder}
        />
        <button
          disabled={!this.state.value}
          className="add-item__button"
          onClick={this.add}
        >
          Add
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <AddItem placeholder="Value" onButtonClick={v => console.log(v)} />,
  document.getElementById('View')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='View'></div>
Herniorrhaphy answered 5/1, 2017 at 15:40 Comment(8)
This is the correct answer! Internal state should be used to update on change of the input and the button should simply check against this. +1Hardison
this.state has a limitation that it renders the UI again so if you're typing in a text box in a child component and have bound to the onChange event, it loses focus from the textbox. If it's in the same component it's fine but when used in a child component it loses focus.Piquant
@Piquant That should not happen. Do you have a working example of that?Herniorrhaphy
Just FYI this is not cross-browser friendly. Many browsers still interpret the presence of disabled as true even if set to false. Like it literally just checks for presence of attribute disabled. You must remove the disabled attribute to be cross-browser friendly.Mortmain
@Mortmain React removes the disabled attribute from the DOM if it's set to false—this is cross-browser friendly.Herniorrhaphy
Not when you are using it like this: disabled={!this.state.value}Mortmain
@Mortmain I don't know what to tell you. Check out the actual, running demo code with your inspector. React is removing the disabled attribute. This is JSX, not HTML. No issue here.Herniorrhaphy
@FabianSchultz is this also possible when your state.value = null instead of '' ?Dimple
A
30

In HTML,

<button disabled/>
<button disabled="true">
<button disabled="false">
<button disabled="21">

All of them boils down to disabled="true" that is because it returns true for a non-empty string. Hence, in order to return false, pass a empty string in a conditional statement like this.input.value ? "true" : "".

render() {
    return (
        <div className="add-item">
            <input
                type="text"
                className="add-item__input"
                ref={(input) => this.input = input}
                placeholder={this.props.placeholder}
            />
            <button 
                disabled={this.input.value ? "true" : ""}
                className="add-item__button"
                onClick={this.add.bind(this)}
            >
                Add
            </button>
        </div>
    );
}
Azov answered 18/2, 2020 at 6:42 Comment(3)
Is buttton misspelt here?Fini
React is different from standard DOM, the disabled property takes an actual boolean in React, so disabled={true} or disabled={false}.Ap
Strings are not accepted for disabled property. Update- disabled={this.input.value ? true : false}Artina
V
13

Here is a functional component variety using react hooks.

The example code I provided should be generic enough for modification with the specific use-case or for anyone searching "How to disable a button in React" who landed here.

import React, { useState } from "react";

const YourComponent = () => {
  const [isDisabled, setDisabled] = useState(false);
  
  const handleSubmit = () => {
    console.log('Your button was clicked and is now disabled');
    setDisabled(true);
  }

  return (
      <button type="button" onClick={handleSubmit} disabled={isDisabled}>
        Submit
      </button>
  );
}

export default YourComponent;
Verla answered 14/6, 2021 at 3:11 Comment(0)
B
7

There are few typical methods how we control components render in React. enter image description here

But, I haven't used any of these in here, I just used the ref's to namespace underlying children to the component.

class AddItem extends React.Component {
    change(e) {
      if ("" != e.target.value) {
        this.button.disabled = false;
      } else {
        this.button.disabled = true;
      }
    }

    add(e) {
      console.log(this.input.value);
      this.input.value = '';
      this.button.disabled = true;
    }

    render() {
        return (
          <div className="add-item">
          <input type="text" className = "add-item__input" ref = {(input) => this.input=input} onChange = {this.change.bind(this)} />
          
          <button className="add-item__button" 
          onClick= {this.add.bind(this)} 
          ref={(button) => this.button=button}>Add
          </button>
          </div>
        );
    }
}

ReactDOM.render(<AddItem / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Biblicist answered 31/1, 2017 at 14:0 Comment(0)
I
6

You shouldn't be setting the value of the input through refs.

Take a look at the documentation for controlled form components here - https://facebook.github.io/react/docs/forms.html#controlled-components

In a nutshell

<input value={this.state.value} onChange={(e) => this.setState({value: e.target.value})} />

Then you will be able to control the disabled state by using disabled={!this.state.value}

Ideatum answered 5/1, 2017 at 15:36 Comment(0)
P
6

very simple solution for this is by using useRef hook

const buttonRef = useRef();

const disableButton = () =>{
  buttonRef.current.disabled = true; // this disables the button
 }

<button
className="btn btn-primary mt-2"
ref={buttonRef}
onClick={disableButton}
>
    Add
</button>

Similarly you can enable the button by using buttonRef.current.disabled = false

Plead answered 26/9, 2020 at 22:47 Comment(0)
L
2

this.input is undefined until the ref callback is called. Try setting this.input to some initial value in your constructor.

From the React docs on refs, emphasis mine:

the callback will be executed immediately after the component is mounted or unmounted

Lymphoma answered 5/1, 2017 at 15:40 Comment(0)
S
1

I have had a similar problem, turns out we don't need hooks to do these, we can make an conditional render and it will still work fine.

<Button
    type="submit"
    disabled={
        name === "" || email === "" || password === "" 
    }
    fullWidth
    variant="contained"
    color="primary"
    className={classes.submit}>
    SignUP
</Button>
Spindly answered 5/7, 2020 at 18:31 Comment(2)
Cool solution, I did the sameMelbamelborn
? true : false is unnecessary since === already returns a boolean value.Tintometer
D
0
const [isButtonDisabled, setButtonDisabled] = useState(false);

const handleClick = () => {
    // Disable the button after performing the action
    setButtonDisabled(true);
};

<button onClick={handleClick} disabled={isButtonDisabled}>
     Click me
</button>

The isButtonDisabled state variable is used to keep track of whether the button should be disabled.

The handleClick function is called when the button is clicked. After performing some action, it sets the isButtonDisabled state to true.

The disabled={isButtonDisabled} attribute on the element ensures that the button is disabled when isButtonDisabled is true.

Druid answered 5/2 at 10:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.