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>
[...]
Share
Improve this question
edited Aug 25, 2019 at 10:09
Pavindu
3,1238 gold badges52 silver badges89 bronze badges
asked Aug 10, 2018 at 6:02
Leandro AranguezLeandro Aranguez
911 silver badge5 bronze badges
2
-
Try moving
this.validate(name)
outside of your setState method – Adam Johnston Commented Aug 10, 2018 at 7:08 - That works but is not validating at the same time when you type in the input.. i mean, when i need a 11 lenght name, only when i type 12 times checks that it is Ok, i was trying this to make the change inmediatly because i can't call it in render (infinite loop) – Leandro Aranguez Commented Aug 10, 2018 at 7:27
2 Answers
Reset to default 3Looks 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.