I am dealing with a lot of problems about updating states in React for validation purposes, and always my state update after the render or something like that, anyway, I saw that many people solve this using a callback function in the setState
, but it always throws the following warning.
Warning: An update (setState, replaceState, or forceUpdate) was scheduled from inside an update function. Update functions should be pure, with zero side-effects. Consider using ponentDidUpdate or a callback.
I tried to update inside ponentDidUpdate
, but that results in an infinite loop.
This is my setState
with the callback function.
state = {
index: 0,
activeInput: '',
errors: {
error: false,
errorNombre: false,
errorCuil: false,
errorEmail: false,
errorContrasena: false,
errorCalle: false,
errorNumero: false,
errorProvincia: false,
errorLocalidad: false
},
values: {...this.props.initialValues}
};
_onChangeValue = (name, value) => {
this.setState(prevState => {
return {
values: {
...prevState.values,
[name]: value
}
}, this.validate(name)
})
};
And this is the Validate Function, is a switch, so i much longer but the problem maybe could be that every case has another setState inside...
validate = input => {
switch(input){
case 'nombre':
let { nombre } = this.state.values
let splitNombre = nombre.split(' ');
if ( splitNombre.length > 1 && splitNombre[1].length === 0 || nombre.length === 0 || splitNombre.length === 1 ) {
this.setState(prevState => ({
errors: { ...prevState.errors, error: true, errorNombre: true }
}));
} else {
this.setState(prevState => ({
errors: { ...prevState.errors, error: false, errorNombre: false }
}));
}
break
As this is a pound ponent is a little messy but here is the files
Input.js
import React, { PureComponent } from 'react';
class Input extends PureComponent {
_onChangeText = text => {
this.props.onChangeValue(this.props.name, text);
};
render() {
const { onChangeValue, name, ...rest } = this.props;
return (
<input {...rest} onChange={(event) => this._onChangeText(event.target.value)} />
);
}
}
export default Input;
Steps.js
import Botones from './Botones'
export default class Step extends PureComponent {
state = {}
render() {
return (
<Fragment>
{this.props.children({
onChangeValue: this.props.onChangeValue,
values: this.props.values,
index: this.props.index,
errors: this.props.errors
})}
<div>
<Botones currentIndex={this.props.currentIndex}
prevStep={this.props.prevStep}
nextStep={this.props.nextStep}
onSubmit={this.props.onSubmit}
isLast={this.props.isLast}
isFirst={this.props.isFirst}
error={this.props.errors.error}
/>
</div>
</Fragment>
)
}
}
FirstStep.js
import Steps from './Steps';
export default class FirstStep extends Component {
constructor(props){
super(props)
this.state = {}
}
render() {
//console.log(this.props)
return (
<Fragment>
<Steps level={this.props.index}/>
<form onSubmit={this.onSubmit} className="mt90">
<div>
<label>Nombre pleto</label>
<input type="text"
name="nombre"
placeholder="Escriba su nombre"
onChange={ event => this.props.onChangeText('nombre', event.target.value) }
value={this.props.values.nombre}
className={ `${ this.props.errors.errorNombre && "error-focus" }` }
/>
{ this.props.errors.errorNombre &&
<span className="error">No es un nombre pleto</span> }
</div>
<div className="mt50">
<label>N° de CUIL</label>
<input type="text"
name="cuil"
placeholder="Ej.: 23-45678901-2"
onChange={ event => this.props.onChangeText('cuil', event.target.value) }
value={this.props.values.cuil}
className={ `${ this.props.errors.errorCuil && "error-focus" }` }
/>
{ this.props.errors.errorCuil &&
<span className="error">No es un CUIL válido</span> }
</div>
</form>
</Fragment>
)
}
}
App.js (has the FirstStep as a child ponent)
[...]
Render()
<RegisterBox.Step>
{ ({ values, onChangeValue, index, errors }) => (
<FirstStep values={values} onChangeText={onChangeValue} index={index} errors={errors} />
)}
</RegisterBox.Step>
[...]
I am dealing with a lot of problems about updating states in React for validation purposes, and always my state update after the render or something like that, anyway, I saw that many people solve this using a callback function in the setState
, but it always throws the following warning.
Warning: An update (setState, replaceState, or forceUpdate) was scheduled from inside an update function. Update functions should be pure, with zero side-effects. Consider using ponentDidUpdate or a callback.
I tried to update inside ponentDidUpdate
, but that results in an infinite loop.
This is my setState
with the callback function.
state = {
index: 0,
activeInput: '',
errors: {
error: false,
errorNombre: false,
errorCuil: false,
errorEmail: false,
errorContrasena: false,
errorCalle: false,
errorNumero: false,
errorProvincia: false,
errorLocalidad: false
},
values: {...this.props.initialValues}
};
_onChangeValue = (name, value) => {
this.setState(prevState => {
return {
values: {
...prevState.values,
[name]: value
}
}, this.validate(name)
})
};
And this is the Validate Function, is a switch, so i much longer but the problem maybe could be that every case has another setState inside...
validate = input => {
switch(input){
case 'nombre':
let { nombre } = this.state.values
let splitNombre = nombre.split(' ');
if ( splitNombre.length > 1 && splitNombre[1].length === 0 || nombre.length === 0 || splitNombre.length === 1 ) {
this.setState(prevState => ({
errors: { ...prevState.errors, error: true, errorNombre: true }
}));
} else {
this.setState(prevState => ({
errors: { ...prevState.errors, error: false, errorNombre: false }
}));
}
break
As this is a pound ponent is a little messy but here is the files
Input.js
import React, { PureComponent } from 'react';
class Input extends PureComponent {
_onChangeText = text => {
this.props.onChangeValue(this.props.name, text);
};
render() {
const { onChangeValue, name, ...rest } = this.props;
return (
<input {...rest} onChange={(event) => this._onChangeText(event.target.value)} />
);
}
}
export default Input;
Steps.js
import Botones from './Botones'
export default class Step extends PureComponent {
state = {}
render() {
return (
<Fragment>
{this.props.children({
onChangeValue: this.props.onChangeValue,
values: this.props.values,
index: this.props.index,
errors: this.props.errors
})}
<div>
<Botones currentIndex={this.props.currentIndex}
prevStep={this.props.prevStep}
nextStep={this.props.nextStep}
onSubmit={this.props.onSubmit}
isLast={this.props.isLast}
isFirst={this.props.isFirst}
error={this.props.errors.error}
/>
</div>
</Fragment>
)
}
}
FirstStep.js
import Steps from './Steps';
export default class FirstStep extends Component {
constructor(props){
super(props)
this.state = {}
}
render() {
//console.log(this.props)
return (
<Fragment>
<Steps level={this.props.index}/>
<form onSubmit={this.onSubmit} className="mt90">
<div>
<label>Nombre pleto</label>
<input type="text"
name="nombre"
placeholder="Escriba su nombre"
onChange={ event => this.props.onChangeText('nombre', event.target.value) }
value={this.props.values.nombre}
className={ `${ this.props.errors.errorNombre && "error-focus" }` }
/>
{ this.props.errors.errorNombre &&
<span className="error">No es un nombre pleto</span> }
</div>
<div className="mt50">
<label>N° de CUIL</label>
<input type="text"
name="cuil"
placeholder="Ej.: 23-45678901-2"
onChange={ event => this.props.onChangeText('cuil', event.target.value) }
value={this.props.values.cuil}
className={ `${ this.props.errors.errorCuil && "error-focus" }` }
/>
{ this.props.errors.errorCuil &&
<span className="error">No es un CUIL válido</span> }
</div>
</form>
</Fragment>
)
}
}
App.js (has the FirstStep as a child ponent)
[...]
Render()
<RegisterBox.Step>
{ ({ values, onChangeValue, index, errors }) => (
<FirstStep values={values} onChangeText={onChangeValue} index={index} errors={errors} />
)}
</RegisterBox.Step>
[...]
this.validate(name)
outside of your setState method
– Adam Johnston
Commented
Aug 10, 2018 at 7:08
Looks like you've made a small typographical error. Your solution should look a bit more like this.
_onChangeValue = (name, value) => {
this.setState(prevState => {
return {
values: {
...prevState.values,
[name]: value
}
}
}, () => this.validate(name));
};
Notice the call to this.validate
has moved out one set of brackets. Secondly, it's been wrapped in an anonymous function, so that it can actually be used as a callback.
I imagine this would have resulted in some confusing behaviour. Because of the ma operator, your setState would not have made any update to values, instead updating state with only the results of your validation!
I think you need to rethink about this problem. To make sure that we are in the same pace, let me reiterate several things.
First of all setState
is asynchronous and more like a signal to React to update the state object and invokes functions of React life cycle.
Nested setState
is discouraged since it's hard to reason about the behavior of asynchronous calls.
You need to restructure your functions to avoid nested setState
. I can't do that for you in this brief answer and you know the best how you want to do so. One thing that you need to notice is that sometimes, you could serialize setState
calls to make sure that the prevState you are seeing in this current setState
call is actually from the previous call. A way to make it work is to wrap setState
with a Promise
and use await/async
to serialize the calls.
Hope this helps a bit.