Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - Warning: An update was scheduled from inside an update function

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 componentDidUpdate or a callback.

I tried to update inside componentDidUpdate, 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 compound component 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 completo</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 completo</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 component)

[...]
Render()
<RegisterBox.Step>
            { ({ values, onChangeValue, index, errors }) => (
              <FirstStep values={values} onChangeText={onChangeValue} index={index} errors={errors} />
            )}
          </RegisterBox.Step>
[...]
like image 722
Leandro Aranguez Avatar asked Aug 10 '18 06:08

Leandro Aranguez


People also ask

What triggers ComponentDidUpdate?

The componentDidUpdate event is triggered whenever there is a state or props update. ComponentDidUpdate() has access to three properties, two of which are most often used more than the third. These are prevProps, prevState and lastly, snapshot, which is used less often.

Why should we not update the state directly React?

If you try to update state directly then it won't re-render the component.

How do you update state immediately in React functional component?

To update state in React components, we'll use either the this. setState function or the updater function returned by the React. useState() Hook in class and function components, respectively.

Why React useState setState does not update immediately?

setState , and React. useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.


1 Answers

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 comma operator, your setState would not have made any update to values, instead updating state with only the results of your validation!

like image 148
j ling Avatar answered Sep 16 '22 12:09

j ling