This question already has answers here:
React - clearing an input value after form submit
(9 answers)
Closed 4 years ago.
I am having some difficulty getting my input form to reset on submit. I would like for the input field to reset to a blank value when the form is submitted successfully, but for the time being I am also fine with it just resetting onSubmit in general, neither of which I have been able to figure out so far. Specifically, what I tried was:
class SubscribeForm extends React.Component {
constructor(props, ...args) {
super(props, ...args)
this.state = {
status: null,
msg: null,
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
this.setState({ value: '' });
}
With the form layout:
<form onSubmit={this.handleSubmit} action={action} method="post" id="alert-form" noValidate>
<input
ref={node => (this.input = node)}
type="email"
defaultValue=""
name="EMAIL"
required={true}
placeholder={messages.inputPlaceholder}
/>
<button
disabled={this.state.status === "sending" || this.state.status === "success"}
onClick={this.onSubmit}
type="submit"
className="btn-group"
id="AlertButton"
>
<p>Sign Up</p>
</button>
</form>
However, calling {this.handleSumbit} in the onSubmit prop of the Form does not appear to have any result when the button is actually clicked. Is there anything obvious i'm missing here or is it a more complex problem?
Provided below is the full relevant code snippet:
class SubscribeForm extends React.Component {
constructor(props, ...args) {
super(props, ...args)
this.state = {
status: null,
msg: null,
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
this.setState({ value: '' });
}
onSubmit = e => {
e.preventDefault()
if (!this.input.value || this.input.value.length < 5 || this.input.value.indexOf("#") === -1) {
this.setState({
status: "error"
})
return
}
const url = getAjaxUrl(this.props.action) + `&EMAIL=${encodeURIComponent(this.input.value)}`;
this.setState(
{
status: "sending",
msg: null
}, () => jsonp(url, {
param: "c"
}, (err, data) => {
if (err) {
this.setState({
status: 'error',
msg: err
})
} else if (data.result !== 'success') {
this.setState({
status: 'error',
msg: data.msg,
})
} else {
this.input.defaultValue = "";
this.setState({
status: 'success',
msg: data.msg,
inputPlaceholder: "E-mail"
})
}
})
)
}
render() {
const { action, messages, className, style, styles } = this.props
const { status, msg } = this.state
return (
<div className={className} style={style}>
<form onSubmit={this.handleSubmit} action={action} method="post" id="alert-form" noValidate>
<input
ref={node => (this.input = node)}
type="email"
defaultValue=""
name="EMAIL"
required={true}
placeholder={messages.inputPlaceholder}
/>
<button
disabled={this.state.status === "sending" || this.state.status === "success"}
onClick={this.onSubmit}
type="submit"
className="btn-group"
id="AlertButton"
>
<p>Sign Up</p>
</button>
</form>
</div>
)
}
}
Try this
handleSubmit(event) {
event.preventDefault();
this.input.value = '';
this.setState({ value: '' });
}
https://codepen.io/va0000ll/pen/zRXNwY
You could make SubscribeForm a controlled component. https://reactjs.org/docs/forms.html
This means adding an onChange that updates this.state.value and setting the value of the input field to this.state.value.
If you do this your setState({value: ''}) would cause a render of the form with the value set back to empty string.
It creates a two way binding. When the input field is updated, so is the component state. When the component state is updated, so is the input field.
Since you are using onSubmit, you can get the input field from the event. Something like event.target.elements.EMAIL.value = ''. I would prefer this over using the ref that you have there.
Related
I am using React JS.
Here is my React class:
class SomeClass extends React.Component{
constructor(props){
super(props);
this.state = {
passAccount: {
email: "Email"
},
errorMessage: ''
};
}
submitRequest = (event) =>{
//this.state.passAccount.email === 'Email' ? this.setState({errorMessage:'Please enter a valid email'}) : axios.post(`http://localhost:5000/otp/generate-passcode/${this.state.passAccount.email.toString()}`, this.state.passAccount)
axios.post(`http://localhost:5000/generate/${String(this.state.passAccount.email)}`)
.then((response) => {
let result = response.data;
}).catch((error) =>{
this.setState({errorMessage: ''});
});
console.log(`submitRequest email: `, this.state.passAccount.email);
}
handleChange = (event) =>{
console.log(`input detected`);
let request = this.state.passAccount;
let requestValue = event.target.value;
this.setState({passAccount: requestValue});
}
render() {
return (
<Form onSubmit={this.handleSubmit}>
<Form.Group>
<Form.Label>Email Address</Form.Label>
<Form.Control type="text" value={this.state.email} onChange={this.handleChange} placeholder="Enter Email Address" style={{width: '25rem'}}/>
</Form.Group>
<Button type="submit" onClick={() => this.submitRequest()}>Get OTP</Button>
<Button type="submit">Sign In</Button>
</Form>
);
}
}
export default SomeClass;
In Chrome console, this is what I am getting:
input detected
submitRequest email: Email //here is what I want to fix
Form Submitted Successfully
My question is:
In the line where it says in the console:
submitRequest email: Email //here is what I want to fix, for some reason the setState method is not working what is the reason behind that ?
Is the error in the handleChange method or in the submitRequest method ? what is a possible fix ?
Thanks.
When you this.setState({passAccount: requestValue}); you are setting passAccount to current value edited in form. But passAccount is an object with email property.
So I would suggest to modify your code in this way:
handleChange = (event) =>{
console.log(`input detected`);
let request = Object.assign({}, this.state.passAccount); // copy this.state.passAccount into request
request.email = event.target.value; // change request email
this.setState({ passAccount: request }); // set passAccount
}
You have declared your state variable passAccount as an object which contains an email property. If you want to update this email property then in your handleChange function, you need to update the state like this:
this.setState({ passAccount: { email: requestValue });
I have two input forms email and password with onBlur to validate them and a Button for onSubmit. My question is, if email form onBlur executed when I start typing on password form, how about that password form when I'll click directly next the onSubmit button, isn't? That means (because Javascript and setState() are asynchronous) the onBlur may not be executed first or at least onSubmit may start its logic execution before finishing the onBlur logic of Password form, so a state in onSubmit may have wrong data cause it didn't wait password form onBlur? (I really hate that fact in web development)
When I look at console.log I see it respected password onBlur, setState() execution and waited for its callback, but I'm not sure if it will work the same every time. (console.log(Blur) line is only in password onBlur not in email for testing)
Edit : I tested again and it appears that sometimes if password form has an error the submit Button wont execute any line in onSubmit code, so I need sometimes after correcting password error to click twice on Submit button to make onSubmit code executed. How to deal with that conflict?
Code:
Form:
const SignInForm = (props) => {
let enabledOrDisabled = props.signInFormCanEvent ? false : 'disabled';
return (
<React.Fragment>
<h1>Welcome to ToDo</h1>
<form onSubmit={props.signInSubmitHandler} className={style.signInForm}>
<span className={style.userFormsErrors}>{props.userNotFoundError}</span>
<div className={style.signInFormImportantElements}>
<span className={style.userFormsErrors}>{props.userEmailError}</span>
<input
name="userEmail"
type="email"
placeholder="email"
value={props.currentUserEmailText}
className={style.signInText}
onChange={(e) => {
props.signInOnChangeHandler(e);
}}
onBlur={(e) => props.signInOnBlurHandler(e)}
disabled={enabledOrDisabled}
/>
<span className={style.userFormsErrors}>{props.userPasswordError}</span>
<input
name="userPassword"
type="password"
placeholder="password"
value={props.currentUserPasswordText}
className={style.signInText}
onChange={(e) => {
props.signInOnChangeHandler(e);
}}
onBlur={(e) => props.signInOnBlurHandler(e)}
disabled={enabledOrDisabled}
/>
<label>
Remember me
<input
type="checkbox"
name="rememberMe"
onChange={(e) => props.signInOnChangeHandler(e)}
disabled={enabledOrDisabled}
/>
</label>
<input type="submit" value="Submit" className={style.signInSubmit} disabled={enabledOrDisabled} />
</div>
<div className={style.signInLinks}>
Forget Password
{/*Create Account*/}
<input type="button" value="SignUp" onClick={props.signUpLinkHandler} />
</div>
</form>
</React.Fragment>
);
};
onBlur :
signInOnBlurHandler(e) {
e.preventDefault();
const { name, value } = e.target;
let tmpErrors = { ...this.state.signInErrors };
switch (name) {
case 'userEmail':
tmpErrors.userEmailError = validEmailRegex.test(value) ? '' : 'Email is not valid!';
this.setState({ signInErrors: tmpErrors });
break;
case 'userPassword':
tmpErrors.userPasswordError = value.length >= 8 ? '' : 'Password must be at least 8 characters long!';
this.setState({ signInErrors: tmpErrors }, () => {
console.log('blur');
});
break;
}
}
onSubmit :
signInSubmitHandler(e) {
e.preventDefault();
console.log('submit');
let signInErrorsTmp = { ...this.state.signInErrors };
signInErrorsTmp.userNotFoundError = '';
this.setState({ signInErrors: signInErrorsTmp }, () => {
let canSubmit = true;
for (let element in this.state.signInErrors) {
if (this.state.signInErrors[element].length > 0) {
canSubmit = false;
}
}
if (canSubmit) {
const config = {
//crossDomain: true,
withCredentials: true
};
axios
.post(
'http://localhost/React_ToDo_APP/to-do/src/PHPFiles/UserSignIn.php',
this.state.signInUserInfo,
config
)
.then((response) => {
if (response.status == 201) {
const tmpSignInUserInfo = { ...this.state.signInUserInfo };
tmpSignInUserInfo.currentUserEmailText = '';
tmpSignInUserInfo.currentUserPasswordText = '';
tmpSignInUserInfo.currentUserRememberMe = false;
this.setState({ signInUserInfo: tmpSignInUserInfo }, () => {
const signedInUserSessionDataTmp = { ...this.state.signedInUserSessionData };
signedInUserSessionDataTmp.userName = response.data.userName;
this.setState({ signedInUserSessionData: signedInUserSessionDataTmp }, () => {
this.setState({ isUserSignedIn: true }, () => {
this.resetAndGetData();
});
});
});
}
})
.catch((error) => {
console.log('Error: ', error);
if (error.response.status == 401) {
let signInErrorsTmp = { ...this.state.signInErrors };
if (error.response.data.emailError.length > 0)
signInErrorsTmp.userEmailError = error.response.data.emailError;
if (error.response.data.passwordError.length > 0)
signInErrorsTmp.userPasswordError = error.response.data.passwordError;
if (
error.response.data.hasOwnProperty('userNotFoundError') &&
error.response.data.userNotFoundError.length > 0
)
signInErrorsTmp.userNotFoundError = error.response.data.userNotFoundError;
this.setState({ signInErrors: signInErrorsTmp });
}
});
}
});
Based on the below, so long as the form is submitted by a click, and not enter keydown, then onBlur handler should apply first, and onSubmit handler will receive correct state.
class Test extends React.Component {
state={
value1:"",
value2:"",
validated1: '',
validated2: ''
}
handleSubmit = (e) => {
e.preventDefault();
console.log("submit", this.state);
}
handleBlur = (e) => {
const target = e.target;
this.setState({["validated" + target.id]: target.value.toUpperCase()})
console.log("blur");
}
handleChange = (e) => {
const target = e.target;
this.setState({["value" + target.id]:target.value});
}
render() {
return(
<form onSubmit={this.handleSubmit}>
<input type="text" onChange={this.handleChange} onBlur={this.handleBlur} value={this.state.value1} id="1"/>
<input type="text" onChange={this.handleChange} onBlur={this.handleBlur} value={this.state.value2} id="2"/>
<input type="submit" value="submit" />
</form>
)
}
}
ReactDOM.render(<Test/>, 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>
In this app, I'm fetching images from the Unsplash API (with an Express back end, React front end). On page load, general images appear (rendered inside the react-infinite-scroll-component), and if you search, a special fetch method (fetchSearchImages) is called to get new images. In either case, they're rendered in a react-infinite-scroll-component instance.
My problem is that after the form holding the search input is submitted, the search input isn't getting cleared. In the input I have value={props.inputValue}, and in the parent component, after the form is submitted fetchSearchImages is called. In fetchSearchImages, I'm trying to reset the input value with this.setState() but the value displayed in the input remains unchanged. I also tried to do so in the handleSubmit() else block, and that didn't do anything either.
View live | GitHub repo
Child search input component:
const SearchInput = props => {
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
};
return (
<form onSubmit={onSubmit}>
<div className="control">
<input autoFocus value={props.inputValue} onChange={e => props.onSearch(e.target.value)} className="input" type="text" placeholder="Search" />
</div>
</form>
);
}
Parent component:
export class Images extends Component {
state = {
images: [],
searchImages: [],
count: 4,
page: 1,
searchPage: 1,
term: '',
search: false,
newSearch: false,
blankSearch: false,
inputValue: ''
};
componentDidMount() {
const { page, count } = this.state;
axios
.get(`/api/photos?page=${page}&count=${count}`)
.then(res => this.setState({ images: res.data }));
// To prevent same images being fetched upon scrolling (in the first call to fetchImages)
this.setState({ page: page + count });
}
fetchImages = () => {
const { page, count, images } = this.state;
this.setState({ page: page + 1 });
axios
.get(`/api/photos?page=${page}&count=${count}`)
.then(res =>
this.setState({ images: images.concat(res.data) })
);
}
fetchSearchImages = () => {
const { searchPage, count, term, searchImages } = this.state;
this.setState({ searchPage: searchPage + 1, inputValue: '' });
axios
.get(`/api/photos/search?term=${term}&page=${searchPage}&count=${count}`)
.then(res =>
this.setState({
searchImages: searchImages.concat(res.data.results)
})
);
}
// Necessary to place fetchSearchImages in a setState callback to ensure other state is set first
handleSubmit = () => {
if (!this.state.inputValue) {
this.setState({
images: [],
blankSearch: true,
newSearch: false,
search: false,
searchImages: [],
searchPage: 1,
page: 1,
}, this.fetchImages);
} else {
this.setState({
term: this.state.inputValue,
searchImages: [],
searchPage: 1,
page: 1,
search: true,
newSearch: true
}, this.fetchSearchImages);
}
}
handleInputChange = (e) => {
this.setState({
inputValue: e
});
}
render() {
return (
<>
<SearchInput onSearch={this.handleInputChange} value={this.state.inputValue} onFormSubmit={this.handleSubmit} />
<div className="images">
<InfiniteScroll
dataLength={this.state.blankSearch ? this.state.images.length : (this.state.newSearch || this.state.search) ? this.state.searchImages.length : this.state.images.length}
next={this.state.search ? this.fetchSearchImages : this.fetchImages}
hasMore={true}
loader={
<div className="loader-dots">
<span className="loader-dot"></span>
<span className="loader-dot"></span>
<span className="loader-dot"></span>
<span className="loader-dot"></span>
</div>
}
>
{this.state.newSearch || this.state.search ? this.state.searchImages.map(image =>
<Image key={image.id + Math.random()} image={image} />
) : this.state.blankSearch ? this.state.images.map(image =>
<Image key={image.id + Math.random()} image={image} />
) : this.state.images.map(image =>
<Image key={image.id + Math.random()} image={image} />
)}
</InfiniteScroll>
</div>
</>
);
}
}
It doesn't look like your input is being properly controlled.
In your SearchInput component, you are referencing an invalid prop. You call the prop value in the parent, but reference it as inputValue in the child.
Change the input to:
<input autoFocus value={props.value} onChange={e => props.onSearch(e.target.value)} className="input" type="text" placeholder="Search" />
Or the parent to:
<SearchInput onSearch={this.handleInputChange} inputValue={this.state.inputValue} onFormSubmit={this.handleSubmit} />
I have a search input I'd like to clear after the value is submitted and the search is performed with the value. In similar questions, it was suggested to set the state of the input value to '', but I think that's what I tried and it didn't do anything.
I only have a parent and child component in my app. The parent component has a method for searching jokes (searchJokes), and it is passed down as a prop with a different name to the child component in the component instance with onFormSubmit={this.searchJokes}. In the child component, when the user enters something into the search input, its event.target.value is passed with onChange={e => props.onInputChange(e.target.value)} corresponding to the onSearchChange method in the parent prop, and the value is used to update the state of searchTerm.
I added searchTerm: '' to the end of the searchJokes method, which fetches a search according to the search term, as you can see in the parent component code below.
Parent component:
class App extends Component {
constructor() {
super();
this.state = {
searchTerm: '',
jokes: [],
isFetchingJokes: false,
isSearch: false
};
this.onSearchChange = this.onSearchChange.bind(this);
this.randomizeJokes = this.randomizeJokes.bind(this);
this.searchJokes = this.searchJokes.bind(this);
}
randomizeJokes() {
this.setState({
isFetchingJokes: true,
isSearch: false
});
fetch(
'https://icanhazdadjoke.com/',
{
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(response => response.json())
.then(json => {
let joke = json.joke;
this.setState({
joke,
isFetchingJokes: false
});
});
}
searchJokes(limit = 15) {
// If nothing entered, user gets "Please fill out this field" message due to "required" attribute on input element
if (this.state.searchTerm !== '') {
this.setState({
isFetchingJokes: true,
isSearch: true
});
fetch(
`https://icanhazdadjoke.com/search?term=${
this.state.searchTerm
}&limit=${limit}`,
{
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(response => response.json())
.then(json => {
let jokes = json.results;
this.setState({
jokes,
isFetchingJokes: false,
searchTerm: '' // <-- DOESN'T CLEAR INPUT
});
});
}
}
onSearchChange(value) {
this.setState({ searchTerm: value });
}
jokeRender() {
return (
<div>
{this.state.isSearch ?
<ul>{this.state.jokes.map(item => <li key={item.id}>{item.joke}</li>)}
</ul> : <p className="random-joke">{this.state.joke}</p>}
</div>
);
}
render() {
return (
<div>
<h1>Dad Jokes</h1>
<RetrievalForm
onFormSubmit={this.searchJokes}
onInputChange={this.onSearchChange}
isSearching={this.state.isFetchingJokes}
onRandomize={this.randomizeJokes}
/>
{this.state.isFetchingJokes ? <p className="searching-message">Searching for jokes...</p> : this.jokeRender()}
</div>
);
};
}
Child component:
const RetrievalForm = props => {
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
};
return (
<>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Enter search term..."
onChange={e => props.onInputChange(e.target.value)}
required
/>
<div>
{/* Specifying type here since it's good practice; different browsers may use default types for buttons */}
<button type="submit" disabled={props.isSearching}>Search</button>
{/* type="button" stops input validation message from being displayed (on Firefox) when randomize button is clicked without anything entered */}
<button type="button" onClick={props.onRandomize} disabled={props.isSearching} className="randomize-button">
Randomize
</button>
</div>
</form>
</>
);
};
Any help would be greatly appreciated.
You need to pass your searchTerm down to the RetrievalForm and in that input set value={searchTerm} so that it's value will be bound to that state.
Basically, you need to store the input value in the component's state. When onSubmit is called, we should revert that value to an empty string.
Example with some React Hooks goodness:
import React, { Component, useState } from 'react';
const RetrievalForm = props => {
const [searchTerm, setSearchTerm] = useState('');
const onChange = e => {
const { value } = e.target;
props.onInputChange(value);
setSearchTerm(value)
}
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
setSearchTerm('');
};
return (
<>
<form onSubmit={onSubmit}>
<input
type="text"
value={searchTerm}
placeholder="Enter search term..."
onChange={onChange}
required
/>
<div>
{/* Specifying type here since it's good practice; different browsers may use default types for buttons */}
<button type="submit" disabled={props.isSearching}>
Search
</button>
{/* type="button" stops input validation message from being displayed (on Firefox) when randomize button is clicked without anything entered */}
<button type="button" onClick={props.onRandomize} disabled={props.isSearching} className="randomize-button">
Randomize
</button>
</div>
</form>
</>
);
};
Example here: https://stackblitz.com/edit/react-db5ire
I'm trying to extend a React input component, which needs to be type password and after certain event on the element or element next to it - it needs to toggle the type of the input (type="text/password").
How this can be handled by React?
I have this as class for my component:
import { React, Component } from 'lib'
export class PasswordInput extends Component {
constructor(props, context){
super(props, context)
const { type, validate, password } = this.props
if(context.onValidate && password) {
context.onValidate(password, validate)
}
if(context.showHide && password) {
context.onValidate(password, validate)
}
}
render() {
let inputType = 'password'
return (
<div className="form">
<label htmlFor="password">{ this.props.label }</label>
<input {...this.constructor.filterProps(this.props)} type={ inputType } />
{ this.props.touched && this.props.error && <div className="error">{ this.props.error }</div> }
<button onClick={ showHide() }>{ this.props.btnLabel }</button>
</div>
)
}
showHide(field) {
return function(event){
console.log(`state before is ${this.state}`)
}
}
// the meld is
// export function meld(...objects) {
// return Object.assign({}, ...objects)
// }
static filterProps(props) {
const result = meld(props)
delete(result.validate)
return result
}
}
PasswordInput.contextTypes = {
onValidate: React.PropTypes.func
}
EDIT
I've edited the render method and added a function that handles the event, but I'm now getting:
Warning: setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
And my browser crashes.
.....
render() {
return (
<div className="form__group">
<label htmlFor="password">{ this.props.label }</label>
<input {...this.constructor.filterProps(this.props)} type={ this.state.inputType } />
{ this.props.touched && this.props.error && <div className="form__message form__message_error">{ this.props.error }</div> }
<button onClick={ this.handleClick() }>{ this.props.btnLabel }</button>
</div>
)
}
handleClick(){
this.setState({ inputType: this.state.inputType === 'password' ? 'text' : 'password' })
}
.....
You can configure the type of the input according to component's state, and set it on some event, for example:
<input {...this.constructor.filterProps(this.props)} type={ this.state.inputType } onChange={ event => this.onChange(event) } />
And implement the onChange method:
onChange(event) {
var length = event.currentTarget.value.length;
this.setState({ inputType: length < 5 ? 'text' : 'password' });
}
You might have to bind the onChange function.