setState no actualiza el estado inmediatamente

104

Me gustaría preguntar por qué mi estado no cambia cuando hago un evento onclick. Hace un tiempo busqué que necesito vincular la función onclick en el constructor pero aún así el estado no se actualiza. Aquí está mi código:

import React from 'react';

import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';

import BoardAddModal from 'components/board/BoardAddModal.jsx';

import style from 'styles/boarditem.css';

class BoardAdd extends React.Component {

    constructor(props){
        super(props);

        this.state = {
            boardAddModalShow: false
        }

        this.openAddBoardModal = this.openAddBoardModal.bind(this);
    }
    openAddBoardModal(){
        this.setState({ boardAddModalShow: true });
// After setting a new state it still return a false value
        console.log(this.state.boardAddModalShow);

    }

    render() {

        return (
            <Col lg={3}>
                <a href="javascript:;" className={style.boardItemAdd} onClick={this.openAddBoardModal}>
                    <div className={[style.boardItemContainer,style.boardItemGray].join(' ')}>
                        Create New Board
                    </div>
                </a>



            </Col>
        )
    }
}

export default BoardAdd
Lotería de Sydney
fuente
5
La respuesta que ha aceptado a esta pregunta no tiene sentido. setStateno devuelve una promesa. Si funcionó, solo funcionó porque awaitintroduce un "tick" asincrónico en la función, y sucedió que la actualización de estado se procesó durante ese tick. No esta garantizado. Como dice esta respuesta , debe usar la devolución de llamada de finalización (si realmente necesita hacer algo después de que se actualice el estado, lo cual es inusual; normalmente, solo desea volver a renderizar, lo que sucede automáticamente).
TJ Crowder
Sería bueno que no aceptara la respuesta aceptada actualmente o aceptara una correcta, porque podría usarse como un duplicado para muchas otras preguntas. Tener una respuesta incorrecta en la parte superior es engañoso.
Guy Incognito

Respuestas:

12

Esta devolución de llamada es realmente complicada. Simplemente use async await en su lugar:

async openAddBoardModal(){
    await this.setState({ boardAddModalShow: true });
    console.log(this.state.boardAddModalShow);
}
Spock
fuente
24
Eso no tiene sentido. React's setStateno devuelve una promesa.
TJ Crowder
16
@TJCrowder tiene razón. setStateno devuelve una promesa, por lo que NO se debe esperar. Dicho esto, creo que puedo ver por qué esto funciona para algunas personas, porque await coloca el funcionamiento interno de setState en la pila de llamadas antes que el resto de la función, por lo que se procesa primero y, por lo tanto, parece que el estado ha sido conjunto. Si setState tenía o implementa nuevas llamadas asincrónicas, esta respuesta fallaría. Para implementar esta función correctamente, puede usar esto:await new Promise(resolve => this.setState({ boardAddModalShow: true }, () => resolve()))
Mike Richards
¿Cómo diablos async this.setState({})resolvió mi problema? Teníamos código de trabajo, se ha hecho más desarrollo pero nada cambió esa parte de la aplicación. Solo uno de los dos objetos en setState se estaba actualizando, por lo que la funcionalidad devuelta asincrónica y ahora ambos objetos se están actualizando correctamente. No tengo ni idea de qué diablos estaba mal.
Ondřej Ševčík
145

Su estado necesita algo de tiempo para mutar y, dado que se console.log(this.state.boardAddModalShow)ejecuta antes de que el estado mute, obtiene el valor anterior como salida. Entonces necesitas escribir la consola en la devolución de llamada a la setStatefunción

openAddBoardModal() {
  this.setState({ boardAddModalShow: true }, function () {
    console.log(this.state.boardAddModalShow);
  });
}

setStatees asincrónico. Significa que no puede llamarlo en una línea y asumir que el estado ha cambiado en la siguiente.

Según los documentos 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 devolver potencialmente el valor existente. No hay garantía de funcionamiento sincrónico de llamadas a setState y las llamadas se pueden agrupar para mejorar el rendimiento.

¿Por qué harían setStateasync

Esto se debe a que setStatealtera el estado y provoca la rendición. Esta puede ser una operación costosa y hacerla sincrónica podría hacer que el navegador no responda.

Por lo tanto, las setStatellamadas son asincrónicas y por lotes para una mejor experiencia y rendimiento de la interfaz de usuario.

Shubham Khatri
fuente
Hasta ahora es una gran idea, PERO ¿qué pasa si no podemos usar console.log porque estás usando reglas de eslint?
maudev
2
@maudev, las reglas de eslint le impiden tener console.logs para que no terminen en producción, pero el ejemplo anterior es puramente para depurar. y console.log y se reemplazará con una acción que tenga en cuenta el estado actualizado
Shubham Khatri
1
¿Qué pasa si la devolución de llamada no funciona como dijiste? ¡el mío no!
Alex Jolig
33

Afortunadamente setState() recibe una devolución de llamada. Y aquí es donde obtenemos el estado actualizado.

Considere este ejemplo.

this.setState({ name: "myname" }, () => {                              
        //callback
        console.log(this.state.name) // myname
      });

Entonces, cuando se activa la devolución de llamada, this.state es el estado actualizado.
Puede obtener mutated/updateddatos en la devolución de llamada.

Mustkeem K
fuente
1
También puede usar esta devolución de llamada para pasar cualquier función initMap(), por ejemplo
Dende
¡Camino a seguir! Finalmente resolvió mi problema. Muchas gracias.
Ahmet Halilović
11

Dado que setSatate es una función asincrónica, debe consolar el estado como una devolución de llamada como esta.

openAddBoardModal(){
    this.setState({ boardAddModalShow: true }, () => {
        console.log(this.state.boardAddModalShow)
    });
}
Adnan Shah
fuente
6

setState()no siempre actualiza inmediatamente el componente. Puede realizar la actualización por lotes o aplazarla hasta más tarde. Esto hace que leer this.state inmediatamente después de llamar a setState()un error potencial. En su lugar, use componentDidUpdateo una setStatedevolución de llamada ( setState(updater, callback)), cualquiera de los cuales está garantizado para activarse después de que se haya aplicado la actualización. Si necesita establecer el estado en función del estado anterior, lea sobre el argumento del actualizador a continuación.

setState()siempre dará lugar a una re-renderización a menos que shouldComponentUpdate()devuelva falso. Si se están utilizando objetos mutables y no se puede implementar la lógica de representación condicional shouldComponentUpdate(), llamar setState()solo cuando el nuevo estado difiere del estado anterior evitará repeticiones innecesarias.

El primer argumento es una función de actualización con la firma:

(state, props) => stateChange

statees una referencia al estado del componente en el momento en que se aplica el cambio. No debe mutarse directamente. En cambio, los cambios deben representarse mediante la construcción de un nuevo objeto basado en la entrada del estado y los accesorios. Por ejemplo, supongamos que quisiéramos incrementar un valor en el estado por props.step:

this.setState((state, props) => {
    return {counter: state.counter + props.step};
});
Aman Seth
fuente
2

Si desea realizar un seguimiento del estado que se está actualizando o no, la otra forma de hacer lo mismo es

_stateUpdated(){
  console.log(this.state. boardAddModalShow);
}

openAddBoardModal(){
  this.setState(
    {boardAddModalShow: true}, 
    this._stateUpdated.bind(this)
  );
}

De esta manera, puede llamar al método "_stateUpdated" cada vez que intente actualizar el estado para la depuración.

Ravin Gupta
fuente
1

setState()es asincrónico. La mejor manera de verificar si el estado se está actualizando sería en el componentDidUpdate()y no poner un console.log(this.state.boardAddModalShow)después this.setState({ boardAddModalShow: true }).

según React Docs

Piense en setState () como una solicitud en lugar de un comando inmediato para actualizar el componente. Para una mejor percepción del rendimiento, React puede retrasarlo y luego actualizar varios componentes en una sola pasada. React no garantiza que los cambios de estado se apliquen inmediatamente

estímulos
fuente
1

Según React Docs

React no garantiza que los cambios de estado se apliquen de inmediato. Esto hace que leer this.state justo después de llamar a setState () sea un potencial pitfally potencialmente puede devolver el existingvalor debido a la asyncnaturaleza. En su lugar, use componentDidUpdateo una setStatedevolución de llamada que se ejecute justo después de que la operación setState sea exitosa. En general, recomendamos usar componentDidUpdate()para dicha lógica.

Ejemplo:

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      counter: 1
    };
  }
  componentDidUpdate() {
    console.log("componentDidUpdate fired");
    console.log("STATE", this.state);
  }

  updateState = () => {
    this.setState(
      (state, props) => {
        return { counter: state.counter + 1 };
      });
  };
  render() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <button onClick={this.updateState}>Update State</button>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Khizer
fuente
0

Para cualquiera que intente hacer esto con ganchos, necesita useEffect.

function App() {
  const [x, setX] = useState(5)
  const [y, setY] = useState(15) 

  console.log("Element is rendered:", x, y)

  // setting y does not trigger the effect
  // the second argument is an array of dependencies
  useEffect(() => console.log("re-render because x changed:", x), [x])

  function handleXClick() {
    console.log("x before setting:", x)
    setX(10)
    console.log("x in *line* after setting:", x)
  }

  return <>
    <div> x is {x}. </div>
    <button onClick={handleXClick}> set x to 10</button>
    <div> y is {y}. </div>
    <button onClick={() => setY(20)}> set y to 20</button>
  </>
}

Salida:

Element is rendered: 5 15
re-render because x changed: 5
(press x button)
x before setting: 5
x in *line* after setting: 5
Element is rendered: 10 15
re-render because x changed: 10
(press y button)
Element is rendered: 10 20
Luke Miles
fuente
-1

cuando estaba ejecutando el código y comprobando mi salida en la consola, mostraba que no estaba definido. Después de buscar y encontrar algo que funcionó para mí.

componentDidUpdate(){}

Agregué este método en mi código después de constructor (). Consulte el ciclo de vida del flujo de trabajo nativo de React.

https://images.app.goo.gl/BVRAi4ea2P4LchqJ8

Pravin Ghorle
fuente
-2
 this.setState({
    isMonthFee: !this.state.isMonthFee,
  }, () => {
    console.log(this.state.isMonthFee);
  })
Atchutha rama reddy Karri
fuente
2
Esto es exactamente lo que explica la respuesta más votada, pero sin ninguna explicación y con 3 años de retraso. No publique respuestas duplicadas a menos que tenga algo relevante que agregar.
Emile Bergeron
-2

Sí, porque setState es una función asincrónica. La mejor manera de establecer el estado justo después de escribir el estado establecido es utilizando Object.assign así: Por ejemplo, si desea establecer una propiedad isValid en true, hágalo así


Object.assign (this.state, {isValid: true})


Puede acceder al estado actualizado justo después de escribir esta línea.

Dipanshu Sharma
fuente