¿Por qué llamar al método react setState no muta el estado inmediatamente?

326

Estoy leyendo la sección de formularios dedocumentación y acabo de probar este código para demostrar el onChangeuso ( JSBIN ).

var React= require('react');

var ControlledForm= React.createClass({
    getInitialState: function() {
        return {
            value: "initial value"
        };
    },

    handleChange: function(event) {
        console.log(this.state.value);
        this.setState({value: event.target.value});
        console.log(this.state.value);

    },

    render: function() {
        return (
            <input type="text" value={this.state.value} onChange={this.handleChange}/>
        );
    }
});

React.render(
    <ControlledForm/>,
  document.getElementById('mount')
);

Cuando actualizo el <input/>valor en el navegador, el segundo console.logdentro de la handleChangedevolución de llamada se imprime igual valueque el primero console.log. ¿Por qué no puedo ver el resultado this.setState({value: event.target.value})en el alcance de la handleChangedevolución de llamada?

tarrsalah
fuente
Si está utilizando ganchos, eche un vistazo al método de configuración useState que no refleja el cambio de inmediato .
Emile Bergeron el

Respuestas:

615

De la documentación de React :

setState()no muta inmediatamente this.statepero crea una transición de estado pendiente. Acceder this.statedespués de llamar a este método puede potencialmente devolver el valor existente. No hay garantía de funcionamiento sincrónico de las llamadas setStatey las llamadas se pueden agrupar para aumentar el rendimiento.

Si desea que se ejecute una función después de que ocurra el cambio de estado, páselo como devolución de llamada.

this.setState({value: event.target.value}, function () {
    console.log(this.state.value);
});
Michael Parker
fuente
Buena respuesta. La observación que necesito hacer es tener cuidado de usar valueLink. Funciona bien si no tiene que formatear / enmascarar la entrada.
Dherik
42
Es posible que también desee consultar componentDidUpdate. Se llamará después de que el estado haya cambiado.
Keysox
1
Pregunta rápida si puedo, veo que una vez que pasamos la función que necesitamos como devolución de llamada a setState, esperaba que la función se ejecute primero antes de llamar a render (). Pero veo que el orden es setState () -> render () -> setStates 'callback (). ¿esto es normal? ¿Qué sucede si queremos controlar nuestro render en función de lo que hacemos en la devolución de llamada? shouldComponentUpdate ?
semuzaboi
2
Cambiar el estado de un componente siempre desencadenará una nueva representación a menos que haya un comportamiento shouldComponentUpdateque especifique lo contrario. ¿Qué es exactamente lo que está tratando de hacer en la devolución de llamada que está pasando setStateque desea que ocurra antes de volver a renderizar?
Michael Parker
44
...¿por qué? ¿Alguien podría justificar esto?
JackHasaKeyboard
49

Como se menciona en la documentación de React, no hay garantía de setStateser despedido sincrónicamente, por lo que console.logpuede devolver el estado antes de la actualización.

Michael Parker menciona pasar una devolución de llamada dentro del setState. Otra forma de manejar la lógica después del cambio de estado es a través del componentDidUpdatemétodo del ciclo de vida, que es el método recomendado en React docs.

En general, recomendamos usar componentDidUpdate () para dicha lógica.

Esto es particularmente útil cuando puede haber sucesivos setStatedisparos, y le gustaría activar la misma función después de cada cambio de estado. En lugar de agregar una devolución de llamada a cada uno setState, puede colocar la función dentro del componentDidUpdate, con lógica específica dentro si es necesario.

// example
componentDidUpdate(prevProps, prevState) {
  if (this.state.value > prevState.value) {
    this.foo();  
  }
}
Yo Wakita
fuente
16

Puede intentar usar ES7 async / await. Por ejemplo, usando su ejemplo:

handleChange: async function(event) {
    console.log(this.state.value);
    await this.setState({value: event.target.value});
    console.log(this.state.value);
}
kurokiiru
fuente
¿En qué se diferencia su respuesta de la otra respuesta de alta calidad?
tod
99
La otra respuesta es con respecto al uso de la devolución de llamada en setState (). Pensé poner esto aquí para aquellos a quienes no se aplica un caso de devolución de llamada. Por ejemplo, cuando enfrenté este problema yo mismo, mi caso de uso involucró un caso de cambio en el estado actualizado justo después de configurarlo. Por lo tanto, se prefirió usar async / await a usar una devolución de llamada.
kurokiiru
¿Esto afectará el rendimiento si siempre uso esperar siempre cuando quiero actualizar algún estado y luego esperar a que se actualice? Y si pongo múltiples setStates en espera en una cadena uno debajo de otro, ¿se renderizará después de cada actualización de setState? o después de la última actualización de setState?
user2774480
En lo que le pregunta al usuario 2774480, creo que todo se reduce a un caso de uso específico para determinar qué implementación usar. Si se usan múltiples setStates en una cadena, el rendimiento se verá afectado, y sí, se representará después de cada setState, pero corrígeme si me equivoco.
kurokiiru
-1

async-await la sintaxis funciona perfectamente para algo como lo siguiente ...

changeStateFunction = () => {
  // Some Worker..

  this.setState((prevState) => ({
  year: funcHandleYear(),
  month: funcHandleMonth()
}));

goNextMonth = async () => {
  await this.changeStateFunction();
  const history = createBrowserHistory();
  history.push(`/calendar?year=${this.state.year}&month=${this.state.month}`);
}

goPrevMonth = async () => {
  await this.changeStateFunction();
  const history = createBrowserHistory();
  history.push(`/calendar?year=${this.state.year}&month=${this.state.month}`);
}
Ritwik
fuente
-1

En pocas palabras, this.setState ({data: value}) es de naturaleza asíncrona, lo que significa que sale de la Pila de llamadas y solo vuelve a la Pila de llamadas a menos que se resuelva.

Lea acerca de Event Loop para tener una idea clara de la naturaleza asincrónica en JS y por qué toma tiempo actualizarse.

https://medium.com/front-end-weekly/javascript-event-loop-explained-4cd26af121d4

Por lo tanto

    this.setState({data:value});
    console.log(this.state.data); // will give undefined or unupdated value

ya que lleva tiempo actualizarlo. Para lograr el proceso anterior:

    this.setState({data:value},function () {
     console.log(this.state.data);
    });
Vishal Bisht
fuente