I'm trying to disable a button when an input field is empty. What is the best approach in React for this?
I'm doing something like the following:
<input ref="email"/>
<button disabled={!this.refs.email}>Let me in</button>
Is this correct?
It's not just duplication of the dynamic attribute, because I'm also curious about transferring/checking the data from one element to another.
You'll need to keep the current value of the input in state (or pass changes in its value up to a parent via a callback function, or sideways, or <your app's state management solution here> such that it eventually gets passed back into your component as a prop) so you can derive the disabled prop for the button.
Example using state:
<meta charset="UTF-8">
<script src="https://fb.me/react-0.13.3.js"></script>
<script src="https://fb.me/JSXTransformer-0.13.3.js"></script>
<div id="app"></div>
<script type="text/jsx;harmony=true">void function() { "use strict";
var App = React.createClass({
getInitialState() {
return {email: ''}
},
handleChange(e) {
this.setState({email: e.target.value})
},
render() {
return <div>
<input name="email" value={this.state.email} onChange={this.handleChange}/>
<button type="button" disabled={!this.state.email}>Button</button>
</div>
}
})
React.render(<App/>, document.getElementById('app'))
}()</script>
Using constants allows to combine multiple fields for verification:
class LoginFrm extends React.Component {
constructor() {
super();
this.state = {
email: '',
password: '',
};
}
handleEmailChange = (evt) => {
this.setState({ email: evt.target.value });
}
handlePasswordChange = (evt) => {
this.setState({ password: evt.target.value });
}
handleSubmit = () => {
const { email, password } = this.state;
alert(`Welcome ${email} password: ${password}`);
}
render() {
const { email, password } = this.state;
const enabled =
email.length > 0 &&
password.length > 0;
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Email"
value={this.state.email}
onChange={this.handleEmailChange}
/>
<input
type="password"
placeholder="Password"
value={this.state.password}
onChange={this.handlePasswordChange}
/>
<button disabled={!enabled}>Login</button>
</form>
)
}
}
ReactDOM.render(<LoginFrm />, document.body);
<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>
<body>
</body>
Another way to check is to inline the function, so that the condition will be checked on every render (every props and state change)
const isDisabled = () =>
// condition check
This works:
<button
type="button"
disabled={this.isDisabled()}
>
Let Me In
</button>
but this will not work:
<button
type="button"
disabled={this.isDisabled}
>
Let Me In
</button>
const Example = () => {
const [value, setValue] = React.useState("");
function handleChange(e) {
setValue(e.target.value);
}
return (
<input ref="email" value={value} onChange={handleChange}/>
<button disabled={!value}>Let me in</button>
);
}
export default Example;
<button disabled={false}>button WORKS</button>
<button disabled={true}>button DOES NOT work</button>
Now just use useState or any other condition to pass true/false into the button, assuming you are using React.
its simple let us assume you have made an state full class by extending Component which contains following
class DisableButton extends Components
{
constructor()
{
super();
// now set the initial state of button enable and disable to be false
this.state = {isEnable: false }
}
// this function checks the length and make button to be enable by updating the state
handleButtonEnable(event)
{
const value = this.target.value;
if(value.length > 0 )
{
// set the state of isEnable to be true to make the button to be enable
this.setState({isEnable : true})
}
}
// in render you having button and input
render()
{
return (
<div>
<input
placeholder={"ANY_PLACEHOLDER"}
onChange={this.handleChangePassword}
/>
<button
onClick ={this.someFunction}
disabled = {this.state.isEnable}
/>
<div/>
)
}
}
Related
i'm building basic chat app with react, and i had many of SO form submit questions and answers but none are helped me, i know it may be dumb type question but really i'm stucked here, any helps are thank you, below is my code.
export default class Call extends Component {
state = {
Message:''
};
onChange = e => {
let { Message, value } = e.target;
console.log(e.target.value); // here i'm getting input value
this.setState({ [Message]: value });
};
onSubmit = e => {
e.preventDefault();
console.log("Message is ", this.state.Message); //getting undefined
};
render() {
return (
<form onSubmit={this.onSubmit}>
<input type="text" name="Message" onChange={this.onChange}/>
<input type="submit" value="Send" />
</form>
)}
Try without brackets:
onChange = e => {
let { Message, value } = e.target;
console.log(e.target.value); // here i'm getting input value
this.setState({ Message: value });
};
You need to initialize state inside class constructor and set it using this.setState({ Message: value }) syntax.
class Call extends React.Component {
constructor(props) {
super(props);
this.state = {
Message: ''
};
}
onChange = e => {
let { value } = e.target;
console.log(e.target.value); // here i'm getting input value
this.setState({ Message: value });
};
onSubmit = e => {
e.preventDefault();
console.log("Message is ", this.state.Message); //getting undefined
};
render() {
return (
<form onSubmit={this.onSubmit}>
<input type="text" name="Message" onChange={this.onChange} />
<input type="submit" value="Send" />
</form>
);
}
}
ReactDOM.render(<Call />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
My first React session data storage, so thanks. I am trying to set up inputing data and then placing it in session storage, so it can be edited, viewed or deleted later. There are 2 pieces of data, "title" and "note" to be inputed into a form. Nothing happens when I type into the form inputs. Any other help welcome also.
class AddNote extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
content: ''
}
}
componentDidMount() {
this.getFormData();
}
//let notes = getSessionItem(keys.notes);
//if (!notes) { notes = ""; }
onTitleChange(event) {
this.setState({ title: event.target.value }, this.storeFormData);
this.storeFormData();
}
onContentChange(event) {
this.setState({ content: event.target.value }, this.storeFormData);
}
storeFormData() {
const form = {
title: this.state.title,
content: this.state.content
}
setSessionItem(keys.user_form, form);
}
getFormData() {
const form = getSessionItem(keys.user_form);
if (form) {
this.setState({
title: form.name,
content: form.content
});
}
}
render() {
return (
<div>
<div>
<h2>ADD NOTE PAGE</h2>
</div>
<form classname="nav1">
<div>
<label><b>Title</b></label>
<input type="text"
value={this.state.title}
onchange={this.onTitleChange.bind(this)}
/>
</div>
<div>
<label><b>Content</b></label>
<input type="text"
value={this.state.content}
onchange={this.onContentChange.bind(this)}
/>
</div>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default AddNote;
and the storage file:
export const keys = {
title: 'title',
notes: 'notes'
}
export const getSessionItem = function (key) {
let item = sessionStorage.getItem(key);
item = JSON.parse(item);
return item;
}
export const setSessionItem = function (key, value) {
value = JSON.stringify(value);
sessionStorage.setItem(key, value);
}
export const removeSessionItem = function (key) {
sessionStorage.removeItem(key);
}
No need to have 2 change handler for your input. You can do it using a common change handler.
<form classname="nav1">
<div>
<label><b>Title</b></label>
<input type="text"
value={this.state.title}
name="title" <---- Provide name here
onChange={this.onChange}
/>
</div>
<div>
<label><b>Content</b></label>
<input type="text"
value={this.state.content}
name="content" <---- Provide name here
onChange={this.onChange}
/>
</div>
<button type="submit">Submit</button>
</form>
Your onChange function should be, and use callback in setState to call your storeFormData function.
onChange = (e) => {
this.setState({
[e.target.name] : e.target.value
}, () => this.storeFormData())
}
Note: In React we use camelCase, for example, onchange should be onChange and classname should be className.
Also make sure you bind this to storeFormData and getFormData functions, or you can use arrow function's to automatically bind this.
Demo
I have a React component that looks like this. It's a simple form with an input element of type email. As usual, when the user types some text, I fire a callback for the onChange event. This is what the code looks like.
import React, { PureComponent, Fragment } from "react";
import CheckCircleOutline from "mdi-react/CheckCircleOutlineIcon";
import AccountOutlineIcon from "mdi-react/AccountOutlineIcon";
import styles from "./ForgotPassword.module.scss";
class ForgotPasswordFrom extends PureComponent {
constructor() {
super();
this.state = {
email: ""
};
}
updateEmailField = e => {
this.setState({ email: e.target.value });
};
resetPassword = async e => {
e.preventDefault();
const { email } = this.state;
this.props.onSubmit(email);
};
render() {
const { showResetMessage, email } = this.props;
return (
<Fragment>
<form className="form aim-form">
{!showResetMessage ? (
<Fragment>
<div className="form__form-group">
<div className="form__form-group-field">
<div className="form__form-group-icon">
<AccountOutlineIcon />
</div>
<input
name="email"
type="email"
onChange={this.updateEmailField}
placeholder="Enter Registered Email Address"
className="email-input"
data-testid="forgot_password_input"
/>
</div>
</div>
<button
type="button"
className="btn btn-primary account__btn account__btn--small login-btn"
onClick={this.resetPassword}
data-testid="forgot_password"
>
Submit
</button>
</Fragment>
) : (
<div className={styles.messageContainer}>
<CheckCircleOutline size={50} />
<div className={styles.emailMessage}>
<div>We have sent an email to {email}.</div>
<div>Click the link in the email to reset your password</div>
</div>
</div>
)}
</form>
</Fragment>
);
}
}
export default ForgotPasswordFrom;
I am trying to write a test for when the input field's change event is simulated. This test should basically ensure that the updateEmailField function is triggered. However, no matter what I try, I cannot get the test to pass. The error I get is that the mock function is not called. Any idea what I'm doing wrong?
it("should have called the function", () => {
const wrapper = mount(<ForgotPasswordForm />);
const instance = wrapper.instance();
instance.updateEmailField = jest.fn()
const input = wrapper.find(`[data-testid='forgot_password_input']`);
input.simulate('change', { target: { value: 'test ' } });
expect(instance.updateEmailField).toHaveBeenCalled();
})
Try this code.
it("should have called the function", () => {
jest.spyOn(ForgotPasswordForm.prototype, 'updateEmailField');
const wrapper = mount(<ForgotPasswordForm />);
afterAll(() => {
ForgotPasswordForm.prototype.updateEmailField.mockRestore();
});
const input = wrapper.find(`[data-testid='forgot_password_input']`);
input.simulate('change', { target: { value: 'test ' } });
expect(wrapper.instance().updateEmailField).toHaveBeenCalled();
})
I'm trying to pass 2 elements within the todo array, however it just returns the term element value. I see no error in the console.
It seems that the
items: [...this.state.items, this.state.term, this.state.name ]
only accepts two parameters.
I'm currently following this
https://reactjs.org/docs/handling-events.html
const { Component } = React;
class App extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
name: '',
items: []
};
}
onChange = (event) => {
this.setState({name: event.target.value, term: event.target.value});
}
onSubmit = (event) => {
event.preventDefault();
this.setState({
term: '',
name: '',
items: [
...this.state.items,
this.state.term,
this.state.name
]
});
}
render() {
return (
<div>
<form className="App" onSubmit={this.onSubmit}>
<input value={this.state.term} onChange={this.onChange}/>
<input value={this.state.name} onChange={this.onChange}/>
<button>Submit</button>
</form>
<pre>{JSON.stringify(this.state.items)}</pre>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I suspect the issue you're asking about is the fact that your onChange event updates both term and name and whatever you type into one input goes into the other. Here's how you can resolve that:
Add a name attribute to your input that corresponds to the key in the state.
Access the value of name in onChange and update the value accordingly.
Solution
const { Component } = React;
class App extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
name: '',
items: []
};
}
onChange = (event) => {
const { name, value } = event.target;
this.setState({ [name]: value });
}
onSubmit = (event) => {
event.preventDefault();
this.setState({
term: '',
name: '',
items: [
...this.state.items,
this.state.term,
this.state.name
]
});
}
render() {
return (
<div>
<form className="App" onSubmit={this.onSubmit}>
<input name="term" value={this.state.term} onChange={this.onChange}/>
<input name="name" value={this.state.name} onChange={this.onChange}/>
<button>Submit</button>
</form>
<pre>{JSON.stringify(this.state.items)}</pre>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I dont think your this.setState has any issue for your example.
You seem to be using this.onChange wrong.
I changed its implementation so that both input can have their own respective handler.
onChange = key => event => {
this.setState({ [key]: event.target.value }); };
Also changed input callbacks to pass key value from the render function below.
render() {
const { term, name, items } = this.state;
return (
<div>
<form className="App" onSubmit={this.onSubmit}>
<input value={term} onChange={this.onChange('term')} />
<input value={name} onChange={this.onChange('name')} />
<button>Submit</button>
</form>
{items.join(", ")}
</div>
);
}
}
Your prior code was problematic which was rendering same value for name and term with whatever you type in any of your input boxes.
I'm learning React and I'm trying to create a program with React that would take texts from two input fields and bind them. I get the input fields but the onClick function is not working.
Would appreciate if someone could point out what I'm doing wrong.
Example
<div id='root'></div>
<script type="text/babel">
class CombineText extends React.Component {
constructor(props) {
super(props);
this.state = {pretext: '', posttext:'', wholetext: '' };
}
combineText = () => {
this.setState({
wholetext: this.state.pretext + this.state.posttext
});
}
textChanged = (event) => {
this.setState({[event.target.name]: event.target.value});
}
render() {
return (
<div>
<p>{this.state.wholetext}</p>
<input type="text" id="pretext" onChange={this.textChanged} />
<input type="text" id="posttext" onChange={this.textChanged} />
<button onClick={this.combineText}>Press me</button>
</div>
);
}
}
ReactDOM.render(<CombineText />, document.getElementById('root'));
</script>
You need to make two changes,
First: use event.target.id while setting the state on change of input since name attribute is not defined on input
Second: Specify button type to be button since its by default submit and onClick on the submit button refreshes the page. Or else you could write event.preventDefault() in the combineText method to prevent the default submit action behavior
class CombineText extends React.Component {
constructor(props) {
super(props);
this.state = {pretext: '', posttext:'', wholetext: '' };
}
combineText = () => {
this.setState({
wholetext: this.state.pretext + this.state.posttext
});
}
textChanged = (event) => {
this.setState({[event.target.id]: event.target.value});
}
render() {
return (
<div>
<p>{this.state.wholetext}</p>
<input type="text" id="pretext" onChange={this.textChanged} />
<input type="text" id="posttext" onChange={this.textChanged} />
<button type="button" onClick={this.combineText}>Press me</button>
</div>
);
}
}
ReactDOM.render(<CombineText />, 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>