I have multistep form and I want to make the inputs required. Validation should be on blur and on click button "next". On blur works as expected but I have problems with validating on button click. That way like below, I don't go to next step, no matter if I fill in the fields or not and I don't know how to solve it.
In one file I have:
import React, { Component } from 'react';
import PersonalDetails from './PersonalDetails';
import CourseDetails from './CourseDetails';
class Form extends Component {
state = {
step: 1,
firstname: '',
lastname: '',
isError: {
firstName: true,
lastName: true
},
errorMessage: {
firstName: '',
lastName: ''
}
};
nextStep = () => {
const { step } = this.state;
this.setState({
step: step + 1
})
}
handleChange = input => e => {
this.setState({
[input]: e.target.value
})
if (input === 'firstname') {
if (this.state.firstname.length >= 1) {
this.setState({
isError: {...this.state.isError, firstName: false}
})
}
}
else if (input === 'lastname') {
if (this.state.lastname.length >= 1) {
this.setState({
isError: {...this.state.isError, lastName: false}
})
}
}
}
validateFirstName = () => {
if (this.state.firstname.length < 2) {
this.setState({
errorMessage: {...this.state.errorMessage, firstName: 'Type your first name (at least 2 characters)'},
isError: {...this.state.isError, firstName: true}
});
}
}
validateLastName = () => {
if (this.state.lastname.length < 2) {
this.setState({
errorMessage: {...this.state.errorMessage, lastName: 'Type your last name (at least 2 characters)'},
isError: {...this.state.isError, lastName: true}
});
}
}
render() {
const {
step,
firstname,
lastname,
errorMessage,
isError
} = this.state;
switch(step) {
case 1:
return (
<PersonalDetails
nextStep={this.nextStep}
handleChange={this.handleChange}
firstname={firstname}
lastname={lastname}
validateFirstName={this.validateFirstName}
validateLastName={this.validateLastName}
errorMessage={errorMessage}
isError={isError}
/>
)
case 2:
return (
<CourseDetails />
)
default: return null
}
}
}
export default Form;
and in other file:
class PersonalDetails extends Component {
continue = e => {
e.preventDefault();
this.props.validateFirstName();
this.props.validateLastName();
if (!this.props.isError.firstName && !this.props.isError.lastName) {
this.props.nextStep();
}
}
render() {
const {
firstname,
lastname,
handleChange,
validateFirstName,
validateLastName,
errorMessage,
isError
} = this.props;
return (
<div>
<form>
<div>
<div>
<label htmlFor='first name'>
First name
</label>
<input type='text' value={firstname} name='first name' onChange={handleChange('firstname')} onBlur={validateFirstName} />
<p>{isError.firstName && errorMessage.firstName}</p>
</div>
<div>
<label htmlFor='last name'>
Last name
</label>
<input type='text' value={lastname} name='last name' onChange={handleChange('lastname')} onBlur={validateLastName} />
<p>{isError.lastName && errorMessage.lastName}</p>
</div>
<div>
<button onClick={this.continue}>Next</button>
</div>
</form>
</div>
)
}
}
export default PersonalDetails;
CourseDetails let's assume it's empty page now:
class CourseDetails extends Component {
render() {
return (
<div>
</div>
)
}
}
export default CourseDetails;
I have multistep form and I want to make the inputs required. Validation should be on blur and on click button "next". On blur works as expected but I have problems with validating on button click. That way like below, I don't go to next step, no matter if I fill in the fields or not and I don't know how to solve it.
In one file I have:
import React, { Component } from 'react';
import PersonalDetails from './PersonalDetails';
import CourseDetails from './CourseDetails';
class Form extends Component {
state = {
step: 1,
firstname: '',
lastname: '',
isError: {
firstName: true,
lastName: true
},
errorMessage: {
firstName: '',
lastName: ''
}
};
nextStep = () => {
const { step } = this.state;
this.setState({
step: step + 1
})
}
handleChange = input => e => {
this.setState({
[input]: e.target.value
})
if (input === 'firstname') {
if (this.state.firstname.length >= 1) {
this.setState({
isError: {...this.state.isError, firstName: false}
})
}
}
else if (input === 'lastname') {
if (this.state.lastname.length >= 1) {
this.setState({
isError: {...this.state.isError, lastName: false}
})
}
}
}
validateFirstName = () => {
if (this.state.firstname.length < 2) {
this.setState({
errorMessage: {...this.state.errorMessage, firstName: 'Type your first name (at least 2 characters)'},
isError: {...this.state.isError, firstName: true}
});
}
}
validateLastName = () => {
if (this.state.lastname.length < 2) {
this.setState({
errorMessage: {...this.state.errorMessage, lastName: 'Type your last name (at least 2 characters)'},
isError: {...this.state.isError, lastName: true}
});
}
}
render() {
const {
step,
firstname,
lastname,
errorMessage,
isError
} = this.state;
switch(step) {
case 1:
return (
<PersonalDetails
nextStep={this.nextStep}
handleChange={this.handleChange}
firstname={firstname}
lastname={lastname}
validateFirstName={this.validateFirstName}
validateLastName={this.validateLastName}
errorMessage={errorMessage}
isError={isError}
/>
)
case 2:
return (
<CourseDetails />
)
default: return null
}
}
}
export default Form;
and in other file:
class PersonalDetails extends Component {
continue = e => {
e.preventDefault();
this.props.validateFirstName();
this.props.validateLastName();
if (!this.props.isError.firstName && !this.props.isError.lastName) {
this.props.nextStep();
}
}
render() {
const {
firstname,
lastname,
handleChange,
validateFirstName,
validateLastName,
errorMessage,
isError
} = this.props;
return (
<div>
<form>
<div>
<div>
<label htmlFor='first name'>
First name
</label>
<input type='text' value={firstname} name='first name' onChange={handleChange('firstname')} onBlur={validateFirstName} />
<p>{isError.firstName && errorMessage.firstName}</p>
</div>
<div>
<label htmlFor='last name'>
Last name
</label>
<input type='text' value={lastname} name='last name' onChange={handleChange('lastname')} onBlur={validateLastName} />
<p>{isError.lastName && errorMessage.lastName}</p>
</div>
<div>
<button onClick={this.continue}>Next</button>
</div>
</form>
</div>
)
}
}
export default PersonalDetails;
CourseDetails let's assume it's empty page now:
class CourseDetails extends Component {
render() {
return (
<div>
</div>
)
}
}
export default CourseDetails;
Share
Improve this question
asked Aug 7, 2020 at 17:09
majamaja
1757 silver badges18 bronze badges
4
- Odd question, but by looking at the code I have a theory. Try filling the fields properly, and click on the button - it should not work at first, then try clicking it again, does it work then ? – Antonio Erdeljac Commented Aug 7, 2020 at 17:15
- It doesn't work. – maja Commented Aug 7, 2020 at 17:16
-
Within
PersonalDetails
, atonChange={handleChange(arg)}
, can you update this toonChange={() => handleChange(arg)}
and let us know? And withinForm
, you will need to "bind" thehandlChange
to that ponent forthis
keyword to reference that function correctly...let us know what happens when done. – MwamiTovi Commented Aug 8, 2020 at 5:39 - Now I can't type anything in inputs. I tried to bind handleChange in constructor and without constructor (handleChange = this.handleChange.bind(this)). – maja Commented Aug 8, 2020 at 12:24
2 Answers
Reset to default 2Since React state updates are asynchronous (they are not applied right away), the props won't change right after you perform the validation. The easiest way would be to return the validation result from your validators like this:
validateFirstName = () => {
if (this.state.firstname.length < 2) {
this.setState({
errorMessage: {...this.state.errorMessage, firstName: 'Type your first name (at least 2 characters)'},
isError: {...this.state.isError, firstName: true}
});
return false;
}
return true;
}
validateLastName = () => {
if (this.state.lastname.length < 2) {
this.setState({
errorMessage: {...this.state.errorMessage, lastName: 'Type your last name (at least 2 characters)'},
isError: {...this.state.isError, lastName: true}
});
return false;
}
return true;
}
And then you can use it to determine whether you should continue to the next step or not:
continue = e => {
e.preventDefault();
const isFirstNameValid = this.props.validateFirstName();
const isLastNameValid = this.props.validateLastName();
if (isFirstNameValid && isLastNameValid) {
this.props.nextStep();
}
}
I would try a different approach. Instead of calling validate functions, and then trying to check for errors right after, I would extract the validation itself, and use it for single responsibility. Take a look at your current code:
continue = e => {
e.preventDefault();
this.props.validateFirstName(); // You set / clear the errors in Form state
this.props.validateLastName(); // You set / clear the errors in Form state
if (!this.props.isError.firstName && !this.props.isError.lastName) { // You expect to have that exact state which you've just updated in 2 lines above
this.props.nextStep();
}
}
this.props.isError
will not get immediately updated in your function in which you've just called another 2 functions which update it. That will be noticeable in second call of that function.
Let's try and switch it up like this:
continue = e => {
e.preventDefault();
if (firstname.length < 2 || lastname.length < 2) {
this.props.validateFirstName();
this.props.validateLastName();
} else {
this.props.nextStep();
}
}
This approach would require you to add firstname
and lastname
props as well.