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?
9 Answers
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=""></script> <script src=""></script> <div id='View'></div>8In HTML,
<button disabled/> <buttton disabled="true"> <buttton disabled="false"> <buttton 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> ); } 2Here 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; You shouldn't be setting the value of the input through refs.
Take a look at the documentation for controlled form components here -
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}
There are few typical methods how we control components render in React. 
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=""></script> <script src=""></script> <div></div>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
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
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 === "" ? true : false } fullWidth variant="contained" color="primary" className={classes.submit}> SignUP </Button> 2just Add:
<button disabled={this.input.value?"true":""} className="add-item__button" onClick={this.add.bind(this)}>Add</button>