ebody: React - state Eigenschaftswert wird über timeout() geändert und soll mit Verzögerung gerendert werden

problematische Seite

Hallo,

in dem Beispiel wird ein state Eigenschaftswert über timeout() geändert und soll mit Verzögerung gerendert werden. Ich habe jetzt noch nicht so viel Erfahrung mit React, aber habe es so verstanden, dass wenn ein state Wert geändert wird, auch der Wert dort geändert wird, wo er verwendet wird. Z.B. <div>{this.props.text}</div>

Es wird aber immer nur der letzte Wert gerendert. Warum sieht man nicht, wie sich der Wert alle 3 Sekunden ändert?

Methode (von <App />

  handleSpeak = () => {
    for (let x = 0; x < 3; x++) {
       console.log('speak sentence: ', this.state.sentences[x]);
       setTimeout(this.setState({text: this.state.sentences[x]}), 3000);
    }
  }

<Smalltalk />

/** Child Componentent from <App /> */
class Smalltalk extends React.Component {
  
  render() {
    return (
      <React.Fragment>
        <div>{this.props.text}</div>
      </React.Fragment>
    );
  }
}

<App />

/** Parent Componentent */
class App extends React.Component {
  
  /** Private component data */
  state = {
    text: 'Whats up?',
    sentences: [
      "Beautiful weather today.",
      "Nice jacket.",
      "Beautiful hairstyle, cut yourself?"
    ],
  }
  
  /** Method - update the state.text value */
  handleSpeak = () => {
    for (let x = 0; x < 3; x++) {
       console.log('speak sentence: ', this.state.sentences[x]);
       setTimeout(this.setState({text: this.state.sentences[x]}), 3000);
    }
  }
  
  /** Render JSX */
  render() {
    return (
      <React.Fragment>
        <Smalltalk text={this.state.text} />
        <button onClick={this.handleSpeak}>Speak</button>
      </React.Fragment>
    );
  }
}
  1. problematische Seite

    Hi,

    Es wird aber immer nur der letzte Wert gerendert. Warum sieht man nicht, wie sich der Wert alle 3 Sekunden ändert?

    Weil er sich nicht alle 3 Sekunden ändert.

    Methode (von <App />

      handleSpeak = () => {
        for (let x = 0; x < 3; x++) {
           console.log('speak sentence: ', this.state.sentences[x]);
           setTimeout(this.setState({text: this.state.sentences[x]}), 3000);
        }
      }
    

    hier werden in sehr kurzem Abstand (so lange, wie der JS-Interpreter braucht, um den nächsten Schleifendurchlauf abzuarbeiten, also irgendwas im Bereich von Milli- oder noch kleineren Sekunden) drei Timeouts für "in 3 Sekunden" erzeugt. Diese laufen dann auch nach 3 Sekunden in sehr kurzem Abstand ab.

    Du könntest: den 2. Timeout nach 6 Sekunden, den 3. nach 9 Sekunden ablaufen lassen.
    Oder den 2. Timeout erst dann setzen, wenn der 1. abgelaufen ist, den 3., wenn der 2. abgelaufen ist.
    Oder …

    cu,
    Andreas a/k/a MudGuard

    1. problematische Seite

      Hallo,

      vielen Dank euch beiden. Ich habe eine Lösung gefunden.

      handleSpeak = () => {   
          for (let x = 0; x < 3; x++) {
            setTimeout(() => {
              this.setState({text: this.state.sentences[x]})
             }, x * 1000);
          }
      }
      

      Gruß ebody

  2. problematische Seite

    Hallo ebody!

    Lass mich raten: Du willst den zweiten Timeout erst setzen, nachdem der erste fertig ist. Dafür mehr Infos in einem ähnlichen Problem von mir.

    Au revoir,
    Samuel Fiedler

    --
    In CSS gibt es ja position: absolute; und position: relative;. Was ist nun die Mischung daraus?
    Ganz klar: position: resolute!
    1. problematische Seite

      Hallo Samuel fiedler!

      Das heißt jetzt, dass du das nicht über setTimeout machen solltest, sondern stattdessen die folgenden Schritte ausführen kannst:

      Als erstes machen wir die Funktion asynchron. Das heißt, dass wir ein function zu async function ändern. Damit wird vor Funktionen ein await möglich. Unser besonderer Helfer ist hier das Promise-Objekt von JavaScript. Mehr dazu erfährst du in dem Wiki-Artikel zum JavaScript Promise.
      Wenn du in einer asynchronen Funktion eine Funktion mit await aufrufst (await METHODE()) und diese Funktion ein Promise zurückgibt, dann wird bei der Funktion so lange gewartet, bis das Promise resolved wird.
      Die Funktion zum resolven kannst du in setTimeout packen. So kann man dann einfach eine Warte-Maschine bauen:

      function delay(n) {                       // Funktionsdefinition
        return new Promise(function(resolve) {  // Erstelle ein neues Promise
          setTimeout(resolve, n);               // Warte n Millisekunden, bevor du das Promise resolvst
        });
      }
      

      Diese Funktion lässt sich jetzt einfach verwenden:

      (async function() {
        console.log("Start");
        await delay(5000);
        console.log("Ende");
      })();
      

      Das würde dann nach 5 Sekunden das „Ende“ ausgeben.

      Falls du aber eine normale Funktion hast, kannst du hinter dem Aufruf ein .then() mit der danach auszuführenden Funktion schreiben. Falls du mit fetch gearbeitet hast, dann kennst du das bestimmt schon. Es wird dann aber nur der Teil nach der Zeit ausgeführt, der in dem .then() ist. Unser Beispiel sähe also so aus:

      console.log("Start");
      delay(5000).then(function() {
        console.log("Ende");
      });
      

      Nur ist es schwierig, damit eine Schleife zu programmieren, daher empfehle ich die obere Version.

      Au revoir,
      Samuel Fiedler

      --
      In CSS gibt es ja position: absolute; und position: relative;. Was ist nun die Mischung daraus?
      Ganz klar: position: resolute!