setState non aggiorna lo stato immediatamente


104

Vorrei chiedere perché il mio stato non cambia quando faccio un evento onclick. Qualche tempo fa ho cercato di associare la funzione onclick nel costruttore, ma lo stato non si aggiorna. Ecco il mio codice:

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

5
La risposta che hai accettato a questa domanda non ha senso. setStatenon restituisce una promessa. Se ha funzionato, ha funzionato solo perché awaitintroduce un "segno di spunta" asincrono nella funzione, ed è successo che l'aggiornamento dello stato è stato elaborato durante quel segno di spunta. Non è garantito. Come dice questa risposta , devi usare il callback di completamento (se hai davvero bisogno di fare qualcosa dopo che lo stato è stato aggiornato, il che è insolito; normalmente, vuoi solo eseguire nuovamente il rendering, che si verifica automaticamente).
TJ Crowder

Sarebbe bene se tu non accettassi la risposta attualmente accettata o accettassi una corretta, perché questa potrebbe essere usata come duplicato per molte altre domande. Avere una risposta errata in alto è fuorviante.
Guy Incognito

Risposte:


12

Questa richiamata è davvero complicata. Usa invece async await:

async openAddBoardModal(){
    await this.setState({ boardAddModalShow: true });
    console.log(this.state.boardAddModalShow);
}

24
Non ha senso. React's setStatenon restituisce una promessa.
TJ Crowder

16
@TJCrowder ha ragione. setStatenon restituisce una promessa, quindi NON dovrebbe essere attesa. Detto questo, penso di poter capire perché questo funziona per alcune persone, perché await inserisce il funzionamento interno di setState nello stack di chiamate prima del resto della funzione, quindi viene elaborato per primo, e quindi sembra che lo stato sia stato impostato. Se setState avesse o implementasse nuove chiamate asincrone, questa risposta fallirebbe.Per implementare correttamente questa funzione, puoi usare questo:await new Promise(resolve => this.setState({ boardAddModalShow: true }, () => resolve()))
Mike Richards

Come diavolo ha async this.setState({})risolto il mio problema? Avevamo un codice funzionante, è stato fatto un ulteriore sviluppo ma nulla ha cambiato quella parte dell'app. Solo uno dei due oggetti in setState veniva aggiornato, rendendo la funzionalità restituita asincrona e ora entrambi gli oggetti vengono aggiornati correttamente. Non ho assolutamente idea di cosa diavolo ci fosse.
Ondřej Ševčík

145

Il tuo stato ha bisogno di un po 'di tempo per mutare e poiché console.log(this.state.boardAddModalShow)viene eseguito prima che lo stato muti, ottieni il valore precedente come output. Quindi è necessario scrivere la console nel callback alla setStatefunzione

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

setStateè asincrono. Significa che non puoi chiamarlo su una linea e presumere che lo stato sia cambiato nella successiva.

Secondo i documenti di React

setState()non muta immediatamente this.statema crea una transizione di stato in sospeso. L'accesso this.statedopo aver chiamato questo metodo può potenzialmente restituire il valore esistente. Non vi è alcuna garanzia di funzionamento sincrono delle chiamate a setState e le chiamate possono essere raggruppate in batch per migliorare le prestazioni.

Perché dovrebbero rendere setStateasincrono

Questo perché setStatealtera lo stato e provoca il rendering. Questa può essere un'operazione costosa e renderla sincrona potrebbe lasciare il browser non rispondente.

Pertanto, le setStatechiamate sono asincrone e in batch per una migliore esperienza dell'interfaccia utente e prestazioni.


Fino ad ora è una grande idea, MA cosa succede se non possiamo usare console.log perché stai usando regole eslint?
maudev

2
@maudev, le regole di eslint ti impediscono di avere console.logs in modo che non finiscano in produzione, ma l'esempio sopra è puramente per il debug. e console.log ed essere sostituito con un'azione che tenga conto dello stato aggiornato
Shubham Khatri

1
E se la richiamata non funziona come hai detto? il mio no!
Alex Jolig

33

Per fortuna setState() prende una richiamata. Ed è qui che otteniamo lo stato aggiornato.

Considera questo esempio.

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

Quindi, quando viene attivato il callback, this.state è lo stato aggiornato.
Puoi ottenere mutated/updateddati in callback.


1
Puoi anche utilizzare questa richiamata per passare qualsiasi funzione initMap(), ad esempio
Dende

Ben fatto! Finalmente ho risolto il mio problema. Molte grazie.
Ahmet Halilović

11

Poiché setSatate è una funzione asincrona, è necessario console lo stato come un callback come questo.

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

6

setState()non sempre aggiorna immediatamente il componente. Potrebbe batch o rinviare l'aggiornamento a più tardi. Questo rende la lettura di this.state subito dopo aver definito setState()una potenziale trappola. Utilizzare invece componentDidUpdateo un setStatecallback ( setState(updater, callback)), uno dei quali è garantito per attivarsi dopo che l'aggiornamento è stato applicato. Se è necessario impostare lo stato in base allo stato precedente, leggere l'argomento di aggiornamento di seguito.

setState()porterà sempre a un nuovo rendering a meno che non shouldComponentUpdate()restituisca false. Se vengono utilizzati oggetti modificabili e la logica di rendering condizionale non può essere implementata shouldComponentUpdate(), la chiamata setState()solo quando il nuovo stato è diverso dallo stato precedente eviterà ripetizioni non necessarie.

Il primo argomento è una funzione di aggiornamento con la firma:

(state, props) => stateChange

stateè un riferimento allo stato del componente al momento in cui viene applicata la modifica. Non dovrebbe essere direttamente mutato. Invece, le modifiche dovrebbero essere rappresentate costruendo un nuovo oggetto basato sull'input di state e props. Ad esempio, supponiamo di voler incrementare un valore in state di props.step:

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

2

Se vuoi monitorare lo stato si sta aggiornando o meno, un altro modo per fare la stessa cosa è

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

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

In questo modo puoi chiamare il metodo "_stateUpdated" ogni volta che provi ad aggiornare lo stato per il debug.


1

setState()è asincrono. Il modo migliore per verificare se lo stato si sta aggiornando sarebbe in componentDidUpdate()e non mettere un console.log(this.state.boardAddModalShow)dopo this.setState({ boardAddModalShow: true }).

secondo React Docs

Pensa a setState () come una richiesta piuttosto che un comando immediato per aggiornare il componente. Per una migliore percezione delle prestazioni, React può ritardarlo e quindi aggiornare diversi componenti in un unico passaggio. React non garantisce che i cambiamenti di stato vengano applicati immediatamente


1

Secondo React Docs

React non garantisce che i cambiamenti di stato vengano applicati immediatamente. Ciò rende la lettura di this.state subito dopo aver chiamato setState () un potenziale pitfalle può potenzialmente restituire il existingvalore a causa della asyncnatura. Invece, usa componentDidUpdateo un filesetState callback che viene eseguito subito dopo che l'operazione setState ha avuto esito positivo. In genere si consiglia di utilizzare invece componentDidUpdate()per tale logica.

Esempio:

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);

0

Per chiunque cerchi di farlo con i ganci, è necessario 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>
  </>
}

Produzione:

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

-1

quando stavo eseguendo il codice e controllando il mio output sulla console, mostrava che non è definito. Dopo aver cercato in giro e trovato qualcosa che ha funzionato per me.

componentDidUpdate(){}

Ho aggiunto questo metodo nel mio codice dopo il costruttore (). controlla il ciclo di vita del flusso di lavoro di React Native.

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


-2
 this.setState({
    isMonthFee: !this.state.isMonthFee,
  }, () => {
    console.log(this.state.isMonthFee);
  })

2
Questo è esattamente ciò che spiega la risposta più votata, ma senza alcuna spiegazione e con 3 anni di ritardo. Non pubblicare risposte duplicate a meno che tu non abbia qualcosa di rilevante da aggiungere.
Emile Bergeron

-2

Sì perché setState è una funzione asincrona. Il modo migliore per impostare lo stato subito dopo aver scritto set state è usare Object.assign in questo modo: Per esempio, vuoi impostare una proprietà isValid su true, fallo in questo modo


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


È possibile accedere allo stato aggiornato subito dopo aver scritto questa riga.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.