I'm new to React and I'm trying to create a modal that will be used to display different content. I have currently set this up to have different 'mode' states and each mode will display different markup, which gets passed into the Modal component as this.props.children
It seems to work to an certain extent but I'm having problems with state handling. The input works fine updating and displaying the current state of the input, but once this content is nested within the Modal component it does some strange things like no longer allowing you to type or show any key input at all, if there is state content, any keypresses are only updating the state with the last character.
I'm guessing this is because the Modal is a stateful component with a new construtor, the reference to 'this' and the 'handleChange' function within the parent app is lost.
Any ideas on where I'm going wrong or how to properly go about this?
Cheers guys :)
The code is here:
import React, { Component } from "react";
import ReactDOM from "react-dom";
class Modal extends Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.show !== this.props.show;
}
componentWillUpdate() {
console.log("[Modal] WillUpdate");
}
render() {
return (
<div
style={{
transform: this.props.show ? "translateY(0)" : "translateY(-100vh)",
opacity: this.props.show ? "1" : "0",
padding: "20px",
border: "1px solid"
}}
>
{this.props.children}
</div>
);
}
}
class App extends Component {
constructor() {
super();
this.handleChange = this.handleChange.bind(this);
this.state = {
newItem: "",
modalOpen: false,
modalMode: ""
};
}
handleChange(e) {
const target = e.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
openModal(mode) {
this.setState({
modalOpen: true,
modalMode: mode
});
}
render() {
console.log(this.state);
let modalContent;
switch (this.state.modalMode) {
case 'addItem':
modalContent = (
<form>
<h1>Add Item</h1>
<input
type="text"
name="newItem"
value={this.state.newItem}
placeholder="Enter an item"
onChange={this.handleChange}
/>
</form>
)
break;
case 'editItem':
modalContent = (
<div>
</div>
)
break;
default:
modalContent = (null)
}
return (
<div>
<button onClick={() => this.openModal('addItem')}>Open Modal</button>
<Modal show={this.state.modalOpen}>
{modalContent}
</Modal>
<h2>Same inputs outside modal</h2>
<input
type="text"
name="newItem"
value={this.state.newItem}
placeholder="Enter an item"
onChange={this.handleChange}
/>
<input
type="text"
name="newItem"
value={this.state.newItem}
placeholder="Enter an item"
onChange={this.handleChange}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
And a code sandbox here - https://codesandbox.io/s/qzx14rmy6
Easy and fast fix could be remove the "value" sync and the state will update just fine.
<input type="text" name="newItem" placeholder="Enter an item" onChange={this.handleChange} />
I pointed out the real problem. In "shouldComponentUpdate", if you return "false" for any reason, component in NOT update with state. This method should be used carefully as it can break your app. Remove the method or return true and component will work just fine.
Related
I'm a bit confused regarding the 'value' parameter and it's assignment.
From here Can't type in React input text field
I understood that when setting a value parameter, that disables the possibility of entering a value to the input box
YET, if we notice the following code (with a focus on the render method - I will put the whole code in the bottom of this question):
import React, { Component } from 'react'
export default class NewBoxForm extends Component {
constructor(props){
super(props);
this.state = {
height: "",
width: "",
backgroundColor: ""
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(evt){ //handles any form of change in the input.
evt.preventDefault(); // prevents refresh of page
this.setState({
[evt.target.name]: evt.target.value
});
}
handleSubmit(evt){
evt.preventDefault();
console.log(this.state)
this.props.handleSubmit(this.state);
//need to use my parent's function to transfer information to parent.
//for that i will send my state, and he will update his state.
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label htmlFor='height'>Height: </label>
<input
name='height'
id='height'
type="number"
value={this.state.height}
onChange={this.handleChange}
/>
<label htmlFor='width'>Width: </label>
<input
name='width'
id='width'
type="number"
value={this.state.width}
onChange={this.handleChange}
/>
<label htmlFor='backgroundcolor'>BackgroundColor: </label>
<input
name='backgroundcolor'
id='backgroundcolor'
type="text"
value={this.state.backgroundColor}
onChange={this.handleChange}
/>
<button>Submit</button>
</form>
)
}
}
which works!
i'm able this way to insert a value to the input tag..
so, my question is, what is the rule of thumb? what is correct the correct way?
good habit vs bad hobbit (:D)
rest of the code:
import React, { Component } from 'react';
import Box from './Box';
import NewBoxForm from './NewBoxForm';
class BoxList extends Component {
constructor(props){
super(props);
this.state = {
boxes: [
{ width: 10, height: 20, backgroundColor: "red" },
{ width: 20, height: "200px", backgroundColor: "purple" },
{ width: "100px", height: "140px", backgroundColor: "yellow" },
]
};
this.handleSubmit = this.handleSubmit.bind(this);
}
// what i will get from my child(Box) ? A BOX, duh.
handleSubmit(newBox){
this.setState(st => ({
boxes: [...st.boxes, newBox]
}))
console.log(this.state);
}
render() {
let boxes2show = this.state.boxes.map(box => (
<Box
width={box.width}
height={box.height}
backgroundColor={box.backgroundColor}/>
))
return (
<div>
<h1>BoxList</h1>
<NewBoxForm handleSubmit={this.handleSubmit}/>
{boxes2show}
</div>
)
}
}
export default BoxList;
import React, { Component } from 'react'
export default class Box extends Component {
constructor(props){
super(props);
}
render() {
const myStyle = {
backgroundColor: this.props.backgroundColor,
width: `${this.props.width}em`,
height: `${this.props.height}em`
};
return (
<div style={myStyle}>
I'm a box.
sa
</div>
)
}
}
cheers
I think you misunderstood the answer. In React, inputs came in two variants (see Docs):
controlled
uncontrolled
Controlled inputs are given a value={valueState} to it, which prevents any changes from the user if valueState isn't updated inside the component. The following example is a controlled input, that can't be changed, because it has no change event handler:
export default function App() {
const value = 'test';
return (
<div className="App">
<input value={value} />
</div>
);
}
To allow user input, you need to react to changes:
export default function App() {
const [value, setValue] = useState('test')
return (
<div className="App">
<input value={value} onChange={(e) => setValue(e.currentTarget.value)} />
</div>
);
}
On the other hand there are uncontrolled inputs. Those don't receive a value input. But they can receive a defaultValue (React speciality) to allow initialy setting a value to it a user can change:
export default function App() {
const value = "test";
const ref = useRef();
const submit = (e) => {
e.preventDefault();
// get data
console.log(ref.current.value);
};
return (
<form className="App" onSubmit={submit}>
<input defaultValue={value} ref={ref} />
<button>Submit</button>
</form>
);
}
I am creating a modal in React that will allow users to add an item to a table. I create a RecipeModal component with a form inside, and I receive no errors when compiling, yet nothing happens when I click the button. I followed a tutorial very closely and have run out of ideas. I've seen people have issues with 'fade' in React that turns the modal completely clear and therefor invisible, but I tried checking in "Inspect" (DevTools? I'm am not sure what it is called) for 'modal' and didn't see it there. I am very new to web developing, so let me know if I should attach something else. I had more input field, but removed them while trying to fix this.
import React, { Component } from 'react';
import { Button, Modal, ModalHeader, ModalBody, Form, FormGroup, Label, Input } from 'reactstrap';
import { connect } from 'react-redux';
import { addRecipe } from '../action/recipeActions';
class RecipeModal extends Component {
state = {
modal: false,
recipe_name: '',
recipe_description: '',
recipe_ingredients: '',
recipe_steps: ''
}
toggle = (e) => {
this.setState({
modal: !this.state.modal
});
}
onChange = (e) => {
this.setState(
{ [e.target.recipe_name]: e.target.value }
);
}
render() {
return (
<div>
<Button
color="dark"
style={{ marginBotton: '2rem' }}
onClick={this.toggle}
>Add Recipe</Button>
<Modal
isOpen={this.state.Modal}
toggle={this.toggle} >
<ModalHeader toggle={this.toggle}>Add a New Recipe</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label for="recipe">Recipe</Label>
<Input
type="text"
recipe_name="recipe_name"
id="recipe"
placeholder="Add recipe name"
OnChange={this.onChange} />
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
export default connect()(RecipeModal);
State is case-sensitive. So it's either you rename your modal state to Modal
state = {
Modal: false,
...
};
or refactor the isOpen prop to <Modal isOpen={this.state.modal} toggle={this.toggle}>
I suggest the latter.
I am new to React and Javascript.
I am trying to have a user fill in a form that describes what a "Mob" should look like. When the user hits submit, I expect handleSubmit() (passed in through a parent) to modify the parent's state, which is an object. However, this behavior is not happening.
Here is the parent component, called App.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mob: new Mob("", "")
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
alert("A name was submitted: " + this.state.vnum + " event value: " + event.state.vnum);
const newMob = new Mob(event.state.vnum, event.state.shortDesc);
this.setState({
mob: newMob
});
}
render() {
return (
<div>
<MobForm mob={this.state.mob} onSubmit={() => this.handleSubmit} />
{console.log("parsed mob vnum: " + this.state.mob.vnum)}
</div>
);
}
}
The child component, called MobForm
class MobForm extends React.Component {
render() {
return (
<div>
<form onSubmit={this.props.onSubmit}>
<CreateStringInputField
name="vnum"
label="vnum:"
/>
<CreateStringInputField
name="shortDesc"
label="Short Desc:"
/>
<input type="submit" value="Submit" />
</form>
{console.log(this.state)}
</div>
);
}
}
Which is calling CreateStringInputField()
function CreateStringInputField(props) {
return (
<div name="row">
<label>
<b>{props.label}</b>
<br />
<input
type="text"
name={props.name}
label={props.label}
/>
</label>
</div>
);
}
And, in case it matters, here is what "Mob" looks like.
class Mob {
constructor(vnum, shortDesc) {
this.vnum = vnum;
this.shortDesc = shortDesc;
};
}
I expect to see {console.log("parsed mob vnum: " + this.state.mob.vnum)} print out the vnum as entered by a user. Instead, I see nothing. How can I achieve this expected output?
With React you won't need to work with plain classes. Instead, the class extends a provided React component (Component or PureComponent) or if you don't need state, then'll use plain functions that just return some JSX.
Working example: https://codesandbox.io/s/simple-form-kdh3w
index.js
import React from "react";
import { render } from "react-dom";
import MobForm from "./components/MobForm";
// simple function that returns "MobForm" and it gets rendered by ReactDOM
function App() {
return <MobForm />;
}
// applies "App" to a <div id="root"></div> in the public/index.html file
render(<App />, document.getElementById("root"));
components/MobForm/index.js (stateful parent component)
import React, { Component } from "react";
import Form from "../Form";
const initialState = {
vnum: "",
shortDesc: ""
};
// a stateful parent that manages child state
class MobForm extends Component {
constructor(props) {
super(props);
this.state = initialState;
// since the class fields are normal functions, they'll lose context
// of "this" when called as a callback. therefore, they'll need
// to be bound to "this" -- via bind, "this" is now referring to
// the Class, instead of the global window's "this")
this.handleChange = this.handleChange.bind(this);
this.handleReset = this.handleReset.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// a reusable class field that stores an input's value via its "name"
// for example: [vnum]: "12345", [shortDesc]: "A number"
// using object destructuring for shorter syntax:
// [event.target.name]: event.target.value
handleChange({ target: { name, value } }) {
this.setState({ [name]: value });
}
// a class field to reset state
handleReset() {
this.setState(initialState);
}
// a class field to "submit" the form and alert what's currently in state
handleSubmit(event) {
// preventDefault prevents page refreshes
event.preventDefault();
// JSON.stringify allows you to print the contents of an object
// otherwise, you'll just see [object Object]
alert(JSON.stringify(this.state, null, 4));
// clears state after submitting form
this.handleReset();
}
render() {
return (
// passing down state via the spread operator, shorthand for
// "vnum={this.state.vum}" and "shortDesc={this.state.shortDesc}",
// as well as, passing down the class fields from above
<Form
{...this.state}
handleChange={this.handleChange}
handleReset={this.handleReset}
handleSubmit={this.handleSubmit}
/>
);
}
}
export default MobForm;
components/Form/index.js (a child function that returns some form JSX)
import React from "react";
import PropTypes from "prop-types";
import Input from "../Input";
// using object destructuring to pull out the MobForm's passed down
// state and fields. shorthand for using one parameter named "props"
// and using dot notation: "props.handleChange", "props.handleReset", etc
function Form({ handleChange, handleReset, handleSubmit, shortDesc, vnum }) {
return (
<form style={{ width: 200, margin: "0 auto" }} onSubmit={handleSubmit}>
<Input name="vnum" label="vnum:" value={vnum} onChange={handleChange} />
<Input
name="shortDesc"
label="Short Desc:"
value={shortDesc}
onChange={handleChange}
/>
<button type="button" onClick={handleReset}>
Reset
</button>{" "}
<button type="submit">Submit</button>
</form>
);
}
// utilizing "PropTypes" to ensure that passed down props match
// the definitions below
Form.propTypes = {
handleChange: PropTypes.func.isRequired,
handleReset: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
shortDesc: PropTypes.string,
vnum: PropTypes.string
};
export default Form;
components/Input/index.js (a reuseable input function)
import React from "react";
import PropTypes from "prop-types";
// once again, using object destructuring to pull out the Form's
// passed down state and class fields.
function Input({ label, name, value, onChange }) {
return (
<div name="row">
<label>
<b>{label}</b>
<br />
<input
type="text"
name={name}
label={label}
value={value}
onChange={onChange}
/>
</label>
</div>
);
}
// utilizing "PropTypes" to ensure that passed down props match
// the definitions below
Input.propTypes = {
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.string,
onChange: PropTypes.func.isRequired
};
export default Input;
In this line
<MobForm mob={this.state.mob} onSubmit={() => this.handleSubmit} />
you are defining an anonymous function that returns your handleSubmit function.
In your form
<form onSubmit={this.props.onSubmit}>
onSubmit will execute the this.props.onSubmit which just returns the handleSubmit function but it wont execute it. To fix it just change MobForm to pass handleSubmit directly instead of passing it in an anonymous function:
<MobForm mob={this.state.mob} onSubmit={this.handleSubmit} />
To handle the submission correctly you need to convert your form inputs to managed components. See docs here
Something like this would be a good start:
class MobForm extends React.Component {
constructor(props) {
super(props);
this.state = {
vnum: '',
shortDesc: '',
};
this.handleChangeVnum = this.handleChangeVnum.bind(this);
this.handleChangeShortDesc = this.handleChangeShortDesc.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChangeVnum(event) {
this.setState({vnum: event.target.value});
}
handleChangeShortDesc(event) {
this.setState({shortDesc: event.target.value});
}
handleSubmit(event) {
this.props.onSubmit(this.state);
event.preventDefault();
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<CreateStringInputField
name="vnum"
label="vnum:"
value={this.state.vnum}
onChange={this.handleChangeVnum}
/>
<CreateStringInputField
name="shortDesc"
label="Short Desc:"
value={this.state.shortDesc}
onChange={this.handleChangeShortDesc}
/>
<input type="submit" value="Submit" />
</form>
{console.log(this.state)}
</div>
);
}
}
And update CreateStringInputField()
function CreateStringInputField(props) {
return (
<div name="row">
<label>
<b>{props.label}</b>
<br />
<input
type="text"
name={props.name}
label={props.label}
value={props.value}
onChange={props.onChange}
/>
</label>
</div>
);
}
I was able to get my desired behavior by passing a function to MobForm which updates this.state.mob.
App
class App extends React.Component {
state = {
mob: new Mob("", "")
};
updateMob = newMob => {
this.setState({
mob: newMob
});
};
render() {
return (
<div>
<MobForm mob={this.state.mob} onSubmit={this.updateMob} />
</div>
);
}
}
I then made MobForm maintain vnum, shortDesc state that I could use in my onChange()
MobForm
state = { vnum: "", shortDesc: "" };
handleSubmit = event => {
event.preventDefault();
const mob = new Mob(this.state.vnum, this.state.shortDesc);
this.props.onSubmit(mob);
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<CreateStringInputField
name="vnum"
value={this.state.vnum}
onChange={event => this.setState({ vnum: event.target.value })}
/>
<CreateStringInputField
name="short desc"
value={this.state.shortDesc}
onChange={event => this.setState({ shortDesc: event.target.value })}
/>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
I have a React application structured like this: App.jsx with 2 components ParentComponentOne.jsx and ParentComponentTwo.jsx. ParentComponentOne.jsx has a component called ChildParentOne.jsx instantiated twice with different props. When clicking on a ChildParentOne.jsx I render the ParentComponentTwo which has inside 2 inputs with the values passed from ChildParentOne and a save button. When clicking on the save button i want to rerender the ChildParentOne component with the new values from the inputs.
App.jsx
class App extends Component {
state = {
show:{
pictureEdit: false
},
imgProp: null
};
childClicked = (props) => {
this.setState(
prevState => ({
show: {
pictureEdit: !prevState.show.pictureEdit,
},
imgProp: props
}))
}
render() {
return (
<div>
<ParentComponentOne childClicked={this.childClicked} />
{this.state.show.pictureEdit ? <ParentComponentTwo imgProp={this.state.imgProp} /> : null}
</div>
);
}
}
export default App;
ParentComponentOne.jsx
class ParentComponentOne extends Component {
imagePopUp = (props) => {
this.props.childClicked(props);
}
render() {
return (
<div>
<ChildParentOne onBtnClick={this.imagePopUp} imgW={340} imgH={83} />
<div>some content</div>
<ChildParentOne onBtnClick={this.imagePopUp} imgW={30} imgH={30} />
</div>
);
}
}
export default ParentComponentOne ;
ChildParentOne.jsx
class ChildParentOne extends Component {
clickFunction = (e) =>{
e.preventDefault();
e.stopPropagation();
this.props.onBtnClick(this.props);
}
render() {
return (
<div onClick={this.clickFunction}>
<img src='some_src' style={{width: this.props.imgW, height: this.props.imgH}}>
</div>
);
}
}
export default ChildParentOne ;
ParentComponentTwo.jsx
class ParentComponentTwo extends Component {
state = {
imgH: this.props.imgProp.imgH,
imgW: this.props.imgProp.imgW,
}
handleInputChange = (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit = (e) => {
e.preventDefault();
//submit logic
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
name='imgH'
value={this.state.imgH}
onChange={this.handleInputChange}
type="number"
placeholder="Image Height"
style={{ width: '100%' }} />
<br />
<input
name='imgW'
value={this.state.imgW}
onChange={this.handleInputChange}
type="number"
placeholder="Image width"
style={{ width: '100%' }} />
<br />
<br />
<button type='submit' className="btn btn-success">Save</button>
</form>
</div>
);
}
}
export default ParentComponentTwo;
TLDR:
React Application
App.jsx - ParentComponentOne.jsx - ChildParentOne.jsx
- ParentComponentTwo.js
onClick ChildParentOne -(send the props)-> ParentComponentOne -(ChildParentOne Props)-> App -(ChildParentOne Props)-> ParentComponentTwo
ParentComponentTwo sets the values recieved on state which are binded to input values.
After I enter new values in the inputs how do i rerender the clicked ChildParentOne component with the new width and height.
When you change the state in your App component, it should trigger a re-rendering of your ChildParentOne automatically.
But since your are setting states, that references to the props, it doesn't get updated as you would think.
In order to get your code to work you need to implement the componentWillReceiveProps method to ParentComponentTwo
componentWillReceiveProps(nextProps) {
if(this.props.imgProp !== nextProps.imgProp) // Check if imgProp differs...
{
this.setState({
imgH: nextProps.imgProps.imgH,
imgW: nextProps.imgProps.imgW
})
}
}
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?
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>
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>
);
}
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;
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="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>
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}
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 === ""
}
fullWidth
variant="contained"
color="primary"
className={classes.submit}>
SignUP
</Button>