useState set-Methode, die die Änderung nicht sofort widerspiegelt

Lesezeit: 13 Minuten

useState set Methode die die Anderung nicht sofort widerspiegelt
Pranjal

Ich versuche Hooks zu lernen und das useState Methode hat mich verwirrt. Ich weise einem Zustand einen Anfangswert in Form eines Arrays zu. Die Set-Methode in useState funktioniert bei mir nicht, sowohl mit als auch ohne die Spread-Syntax.

Ich habe auf einem anderen PC eine API erstellt, die ich anrufe und die Daten abrufe, die ich in den Zustand versetzen möchte.

Hier ist mein Code:

<div id="root"></div>

<script type="text/babel" defer>
// import React, { useState, useEffect } from "react";
// import ReactDOM from "react-dom";
const { useState, useEffect } = React; // web-browser variant

const StateSelector = () => {
  const initialValue = [
    {
      category: "",
      photo: "",
      description: "",
      id: 0,
      name: "",
      rating: 0
    }
  ];

  const [movies, setMovies] = useState(initialValue);

  useEffect(() => {
    (async function() {
      try {
        // const response = await fetch("http://192.168.1.164:5000/movies/display");
        // const json = await response.json();
        // const result = json.data.result;
        const result = [
          {
            category: "cat1",
            description: "desc1",
            id: "1546514491119",
            name: "randomname2",
            photo: null,
            rating: "3"
          },
          {
            category: "cat2",
            description: "desc1",
            id: "1546837819818",
            name: "randomname1",
            rating: "5"
          }
        ];
        console.log("result =", result);
        setMovies(result);
        console.log("movies =", movies);
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);

  return <p>hello</p>;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
</script>

<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>

Weder setMovies(result) Noch setMovies(...result) funktioniert.

Ich erwarte die result Variable, die in die geschoben werden soll movies Reihe.

  • Können Sie sehen, wie sich die Änderungen bewegen? console.log("movies =", movies); außerhalb der useEffect Haken?

    – Domenico Ruggiano

    3. April 2021 um 8:33 Uhr


  • Dies wird als verwendet Beispiel in einer Metafrage.

    – Peter Mortensen

    17. September 2021 um 18:27 Uhr


useState set Methode die die Anderung nicht sofort widerspiegelt
Shubham Khatri

Ähnlich wie setState in Klassenkomponenten, die durch Erweitern erstellt wurden React.Component oder React.PureComponentdie Statusaktualisierung mit dem von bereitgestellten Updater useState Hook ist ebenfalls asynchron und wird nicht sofort reflektiert.

Außerdem ist das Hauptproblem hier nicht nur die asynchrone Natur, sondern die Tatsache, dass Zustandswerte von Funktionen auf der Grundlage ihrer aktuellen Closures verwendet werden und Statusaktualisierungen sich in der nächsten erneuten Darstellung widerspiegeln, wodurch die vorhandenen Closures nicht betroffen, sondern neu sind welche erstellt werden. Im aktuellen Zustand werden die Werte innerhalb von Hooks jetzt durch vorhandene Closures abgerufen, und wenn ein erneutes Rendern erfolgt, werden die Closures basierend darauf aktualisiert, ob die Funktion erneut erstellt wird oder nicht.

Auch wenn Sie a hinzufügen setTimeout die funktion, obwohl das timeout nach einiger zeit ablaufen wird, nach der das erneute rendern schon passiert wäre, das setTimeout wird immer noch den Wert von seiner vorherigen Schließung und nicht den aktualisierten verwenden.

setMovies(result);
console.log(movies) // movies here will not be updated

Wenn Sie bei der Statusaktualisierung eine Aktion ausführen möchten, müssen Sie den useEffect-Hook verwenden, ähnlich wie bei using componentDidUpdate in Klassenkomponenten, da der von useState zurückgegebene Setter kein Callback-Muster hat

useEffect(() => {
    // action on update of movies
}, [movies]);

Was die Syntax zum Aktualisieren des Status betrifft, setMovies(result) wird die vorherige ersetzen movies Wert im Zustand mit denen, die aus der asynchronen Anfrage verfügbar sind.

Wenn Sie die Antwort jedoch mit den zuvor vorhandenen Werten zusammenführen möchten, müssen Sie die Callback-Syntax der Statusaktualisierung zusammen mit der korrekten Verwendung der Spread-Syntax wie verwenden

setMovies(prevMovies => ([...prevMovies, ...result]));

  • Hallo, was ist mit dem Aufrufen von useState in einem Form-Submit-Handler? Ich arbeite an der Validierung eines komplexen Formulars und rufe innerhalb von submitHandler useState Hooks auf und leider sind Änderungen nicht sofort!

    – da45

    19. April 2019 um 16:42 Uhr


  • useEffect ist jedoch möglicherweise nicht die beste Lösung, da asynchrone Aufrufe nicht unterstützt werden. Wenn wir also eine asynchrone Validierung vornehmen möchten movies Zustandsänderung, wir haben keine Kontrolle darüber.

    – RA.

    20. August 2019 um 3:10 Uhr

  • Bitte beachten Sie, dass die Beratung sehr gut ist, die Erklärung der Ursache jedoch verbessert werden kann – nichts mit der Tatsache zu tun, ob oder nicht the updater provided by useState hook ist im Gegensatz zu asynchron this.state das hätte mutiert sein können, wenn this.setState Synchron war die Schließung um const movies würde gleich bleiben, auch wenn useState eine synchrone Funktion bereitgestellt – siehe das Beispiel in meiner Antwort

    – Aprillöwe

    20. November 2019 um 19:26 Uhr


  • setMovies(prevMovies => ([...prevMovies, ...result])); hat bei mir funktioniert

    – Michir

    31. März 2020 um 20:07 Uhr

  • Es protokolliert das falsche Ergebnis, weil Sie a protokollieren altbackener Verschluss nicht, weil der Setter asynchron ist. Wenn Async das Problem war, könnten Sie nach einem Timeout protokollieren, aber Sie könnten ein Timeout für eine Stunde festlegen und trotzdem das falsche Ergebnis protokollieren, da Async nicht das Problem verursacht.

    – HMR

    12. Mai 2020 um 7:02 Uhr


1646895915 761 useState set Methode die die Anderung nicht sofort widerspiegelt
Aprillion

Zusätzliche Details zur vorherigen Antwort:

Während React’s setState ist asynchron (sowohl Klassen als auch Hooks), und es ist verlockend, diese Tatsache zu verwenden, um das beobachtete Verhalten zu erklären, es ist nicht das Grund warum es passiert.

TLDR: Der Grund ist a Schließung Bereich um eine unveränderliche const Wert.


Lösungen:

  • Lesen Sie den Wert in der Renderfunktion (nicht in verschachtelten Funktionen):

      useEffect(() => { setMovies(result) }, [])
      console.log(movies)
    
  • fügen Sie die Variable zu Abhängigkeiten hinzu (und verwenden Sie die Reaktionshaken/erschöpfende Deps Eslint-Regel):

      useEffect(() => { setMovies(result) }, [])
      useEffect(() => { console.log(movies) }, [movies])
    
  • Verwenden Sie eine temporäre Variable:

      useEffect(() => {
        const newMovies = result
        console.log(newMovies)
        setMovies(newMovies)
      }, [])
    
  • Verwenden Sie eine veränderliche Referenz (wenn wir keinen Status benötigen und uns nur den Wert merken möchten – das Aktualisieren einer Referenz löst kein erneutes Rendern aus):

      const moviesRef = useRef(initialValue)
      useEffect(() => {
        moviesRef.current = result
        console.log(moviesRef.current)
      }, [])
    

Erklärung warum es passiert:

Wenn Async der einzige Grund wäre, wäre es möglich await setState().

Allerdings beides props und state sind während 1 Rendervorgang als unveränderlich angenommen.

Behandeln this.state als ob es unveränderlich wäre.

Bei Hooks wird diese Annahme durch using verstärkt konstante Werte mit dem const Stichwort:

const [state, setState] = useState('initial')

Der Wert kann zwischen 2 Renderings unterschiedlich sein, bleibt aber innerhalb des Renderings selbst und in jedem Rendering konstant Schließungen (Funktionen, die auch nach dem Rendern länger leben, z useEffectEvent-Handler, innerhalb von Promise oder setTimeout).

Erwägen Sie folgende Fälschung, aber synchronReaktionsähnliche Implementierung:

// sync implementation:

let internalState
let renderAgain

const setState = (updateFn) => {
  internalState = updateFn(internalState)
  renderAgain()
}

const useState = (defaultState) => {
  if (!internalState) {
    internalState = defaultState
  }
  return [internalState, setState]
}

const render = (component, node) => {
  const {html, handleClick} = component()
  node.innerHTML = html
  renderAgain = () => render(component, node)
  return handleClick
}

// test:

const MyComponent = () => {
  const [x, setX] = useState(1)
  console.log('in render:', x) // ✅
  
  const handleClick = () => {
    setX(current => current + 1)
    console.log('in handler/effect/Promise/setTimeout:', x) // ❌ NOT updated
  }
  
  return {
    html: `<button>${x}</button>`,
    handleClick
  }
}

const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()
<div id="root"></div>

  • Ausgezeichnete Antwort. IMO, diese Antwort wird Ihnen in Zukunft den größten Kummer ersparen, wenn Sie genug Zeit investieren, um sie durchzulesen und zu absorbieren.

    – Scott Mallon

    20. Januar 2020 um 19:10 Uhr

  • @AlJoslin auf den ersten Blick scheint das ein separates Problem zu sein, auch wenn es durch den Schließbereich verursacht werden könnte. Wenn Sie eine konkrete Frage haben, erstellen Sie bitte eine neue Stackoverflow-Frage mit Codebeispiel und allen …

    – Aprillöwe

    16. Juni 2020 um 7:59 Uhr

  • eigentlich habe ich gerade eine Umschreibung mit useReducer abgeschlossen, gefolgt von @kentcdobs Artikel (siehe unten), der mir wirklich ein solides Ergebnis lieferte, das kein bisschen unter diesen Schließungsproblemen leidet. (ref: kentcdodds.com/blog/how-to-use-react-context-effectly)

    – Al Joslin

    16. Juni 2020 um 16:52 Uhr


  • @ACV Lösung 2 funktioniert gut für die ursprüngliche Frage. Wenn Sie ein anderes Problem lösen müssen, YMMW, aber ich bin mir immer noch zu 100% sicher, dass der zitierte Code wie dokumentiert funktioniert und das Problem woanders liegt.

    – Aprillöwe

    22. Oktober 2020 um 20:25 Uhr


  • Dies hätte “Akzeptiert” sein sollen, Antwort @Pranjal

    – vikramvi

    25. März 2021 um 5:27 Uhr

1646895916 289 useState set Methode die die Anderung nicht sofort widerspiegelt
Aminadav Glickshtein

Ich weiß, dass es schon sehr gute Antworten gibt. Aber ich möchte eine andere Idee geben, wie das gleiche Problem gelöst werden kann, und mit meinem Modul auf den neuesten “Film” -Status zugreifen reagieren-useStateRef.

Wie Sie verstehen, können Sie mit der Verwendung des React-Status die Seite jedes Mal rendern, wenn sich der Status ändert. Durch die Verwendung von React ref können Sie jedoch immer die neuesten Werte abrufen.

Also das Modul react-useStateRef lassen Sie state’s und ref’s zusammen verwenden. Es ist abwärtskompatibel mit React.useStatealso kannst du die einfach ersetzen import Aussage

const { useEffect } = React
import { useState } from 'react-usestateref'

  const [movies, setMovies] = useState(initialValue);

  useEffect(() => {
    (async function() {
      try {

        const result = [
          {
            id: "1546514491119",
          },
        ];
        console.log("result =", result);
        setMovies(result);
        console.log("movies =", movies.current); // will give you the latest results
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);

Mehr Informationen:

1646895916 237 useState set Methode die die Anderung nicht sofort widerspiegelt
Al Joslin

Ich habe gerade eine Umschreibung mit useReducer abgeschlossen, gefolgt vom @kentcdobs-Artikel (siehe unten), der mir wirklich ein solides Ergebnis lieferte, das kein bisschen unter diesen Schließungsproblemen leidet.

Sehen: https://kentcdodds.com/blog/how-to-use-react-context-effektiv

Ich habe seine lesbare Boilerplate auf mein bevorzugtes DRYness-Niveau komprimiert – das Lesen seiner Sandbox-Implementierung wird Ihnen zeigen, wie es tatsächlich funktioniert.

import React from 'react'

// ref: https://kentcdodds.com/blog/how-to-use-react-context-effectively

const ApplicationDispatch = React.createContext()
const ApplicationContext = React.createContext()

function stateReducer(state, action) {
  if (state.hasOwnProperty(action.type)) {
    return { ...state, [action.type]: state[action.type] = action.newValue };
  }
  throw new Error(`Unhandled action type: ${action.type}`);
}

const initialState = {
  keyCode: '',
  testCode: '',
  testMode: false,
  phoneNumber: '',
  resultCode: null,
  mobileInfo: '',
  configName: '',
  appConfig: {},
};

function DispatchProvider({ children }) {
  const [state, dispatch] = React.useReducer(stateReducer, initialState);
  return (
    <ApplicationDispatch.Provider value={dispatch}>
      <ApplicationContext.Provider value={state}>
        {children}
      </ApplicationContext.Provider>
    </ApplicationDispatch.Provider>
  )
}

function useDispatchable(stateName) {
  const context = React.useContext(ApplicationContext);
  const dispatch = React.useContext(ApplicationDispatch);
  return [context[stateName], newValue => dispatch({ type: stateName, newValue })];
}

function useKeyCode() { return useDispatchable('keyCode'); }
function useTestCode() { return useDispatchable('testCode'); }
function useTestMode() { return useDispatchable('testMode'); }
function usePhoneNumber() { return useDispatchable('phoneNumber'); }
function useResultCode() { return useDispatchable('resultCode'); }
function useMobileInfo() { return useDispatchable('mobileInfo'); }
function useConfigName() { return useDispatchable('configName'); }
function useAppConfig() { return useDispatchable('appConfig'); }

export {
  DispatchProvider,
  useKeyCode,
  useTestCode,
  useTestMode,
  usePhoneNumber,
  useResultCode,
  useMobileInfo,
  useConfigName,
  useAppConfig,
}

Mit einer ähnlichen Verwendung wie dieser:

import { useHistory } from "react-router-dom";

// https://react-bootstrap.github.io/components/alerts
import { Container, Row } from 'react-bootstrap';

import { useAppConfig, useKeyCode, usePhoneNumber } from '../../ApplicationDispatchProvider';

import { ControlSet } from '../../components/control-set';
import { keypadClass } from '../../utils/style-utils';
import { MaskedEntry } from '../../components/masked-entry';
import { Messaging } from '../../components/messaging';
import { SimpleKeypad, HandleKeyPress, ALT_ID } from '../../components/simple-keypad';

export const AltIdPage = () => {
  const history = useHistory();
  const [keyCode, setKeyCode] = useKeyCode();
  const [phoneNumber, setPhoneNumber] = usePhoneNumber();
  const [appConfig, setAppConfig] = useAppConfig();

  const keyPressed = btn => {
    const maxLen = appConfig.phoneNumberEntry.entryLen;
    const newValue = HandleKeyPress(btn, phoneNumber).slice(0, maxLen);
    setPhoneNumber(newValue);
  }

  const doSubmit = () => {
    history.push('s');
  }

  const disableBtns = phoneNumber.length < appConfig.phoneNumberEntry.entryLen;

  return (
    <Container fluid className="text-center">
      <Row>
        <Messaging {...{ msgColors: appConfig.pageColors, msgLines: appConfig.entryMsgs.altIdMsgs }} />
      </Row>
      <Row>
        <MaskedEntry {...{ ...appConfig.phoneNumberEntry, entryColors: appConfig.pageColors, entryLine: phoneNumber }} />
      </Row>
      <Row>
        <SimpleKeypad {...{ keyboardName: ALT_ID, themeName: appConfig.keyTheme, keyPressed, styleClass: keypadClass }} />
      </Row>
      <Row>
        <ControlSet {...{ btnColors: appConfig.buttonColors, disabled: disableBtns, btns: [{ text: 'Submit', click: doSubmit }] }} />
      </Row>
    </Container>
  );
};

AltIdPage.propTypes = {};

Jetzt bleibt alles reibungslos überall auf allen meinen Seiten bestehen

1646895917 396 useState set Methode die die Anderung nicht sofort widerspiegelt
Muhammad Imran Siddique

React useEffect hat seinen eigenen Zustand/Lebenszyklus, der mit der Mutation des Zustands zusammenhängt, es wird den Zustand nicht aktualisieren, bis der Effekt zerstört ist.

Übergeben Sie einfach ein einzelnes Argument im Parameterzustand oder lassen Sie es ein schwarzes Array und es wird perfekt funktionieren.

React.useEffect(() => {
    console.log("effect");
    (async () => {
        try {
            let result = await fetch("/query/countries");
            const res = await result.json();
            let result1 = await fetch("/query/projects");
            const res1 = await result1.json();
            let result11 = await fetch("/query/regions");
            const res11 = await result11.json();
            setData({
                countries: res,
                projects: res1,
                regions: res11
            });
        } catch {}
    })(data)
}, [setData])
# or use this
useEffect(() => {
    (async () => {
        try {
            await Promise.all([
                fetch("/query/countries").then((response) => response.json()),
                fetch("/query/projects").then((response) => response.json()),
                fetch("/query/regions").then((response) => response.json())
            ]).then(([country, project, region]) => {
                // console.log(country, project, region);
                setData({
                    countries: country,
                    projects: project,
                    regions: region
                });
            })
        } catch {
            console.log("data fetch error")
        }
    })()
}, [setData]);

Alternativ können Sie React.useRef() ausprobieren, um den Reaktionshaken sofort zu ändern.

const movies = React.useRef(null);
useEffect(() => {
movies.current="values";
console.log(movies.current)
}, [])

  • Das letzte Codebeispiel benötigt weder async noch await, da Sie die Promise-API verwenden. Das wird nur im ersten benötigt

    – Oligofren

    27. September 2021 um 7:57 Uhr


  • Mann, du hast mich gerade um Mitternacht gerettet! Vielen Dank @muhammad

    – Raju Ahmed

    30. September 2021 um 20:40 Uhr

Die Schließung ist nicht der einzige Grund.

Basierend auf dem Quellcode von useState (unten vereinfacht). Mir scheint, dass der Wert nie sofort zugewiesen wird.

Was passiert, ist, dass beim Aufrufen eine Aktualisierungsaktion in die Warteschlange gestellt wird setValue. Und nachdem der Zeitplan in Kraft tritt und nur wenn Sie zum nächsten Rendern gelangen, wird diese Aktualisierungsaktion dann auf diesen Status angewendet.

Was bedeutet, dass wir kein Problem mit der Schließung haben, reagieren Sie auf Version von useState wird Ihnen nicht sofort den neuen Wert geben. Der neue Wert existiert nicht einmal bis zum nächsten Rendern.

  function useState(initialState) {
    let hook;
    ...

    let baseState = hook.memoizedState;
    if (hook.queue.pending) {
      let firstUpdate = hook.queue.pending.next;

      do {
        const action = firstUpdate.action;
        baseState = action(baseState);            // setValue HERE
        firstUpdate = firstUpdate.next;
      } while (firstUpdate !== hook.queue.pending);

      hook.queue.pending = null;
    }
    hook.memoizedState = baseState;

    return [baseState, dispatchAction.bind(null, hook.queue)];
  }

function dispatchAction(queue, action) {
  const update = {
    action,
    next: null
  };
  if (queue.pending === null) {
    update.next = update;
  } else {
    update.next = queue.pending.next;
    queue.pending.next = update;
  }
  queue.pending = update;

  isMount = false;
  workInProgressHook = fiber.memoizedState;
  schedule();
}

Es gibt auch einen Artikel, der das Obige auf ähnliche Weise erklärt, https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8

  • Das letzte Codebeispiel benötigt weder async noch await, da Sie die Promise-API verwenden. Das wird nur im ersten benötigt

    – Oligofren

    27. September 2021 um 7:57 Uhr


  • Mann, du hast mich gerade um Mitternacht gerettet! Vielen Dank @muhammad

    – Raju Ahmed

    30. September 2021 um 20:40 Uhr

1646895917 880 useState set Methode die die Anderung nicht sofort widerspiegelt
Peter Mortensen

Es gibt viele gute Antworten, die zeigen, wie Sie Ihren Code reparieren können, aber es gibt ein NPM-Paket, mit dem Sie das Problem einfach durch Ändern von beheben können import. Es heißt reagieren-useStateRef.

In deinem Fall:

import useState from 'react-usestateref'
const [movies, setMovies,moviesRef] = useState(initialValue);
....
useEffect(() => {
   setMovies(...)
   console.log(moviesRef.current) // It will have the last value
})

Wie Sie sehen, können Sie mit dieser Bibliothek auf den neuesten Stand zugreifen.

  • Es funktioniert nicht Ich habe useStateRef installiert und importiert. aber keine änderung in diesem problem. konst [pageCountOwnAlbum, setpageCountOwnAlbum, refOwnAlbum] = useStateRef(1) // Paginierung der eigenen Albumdaten Abrufen von console.log(“Value—“, refOwnAlbum.current);

    – Channi

    31. Mai 2021 um 11:29 Uhr

  • Geben Sie das gleiche wie zuvor

    – Channi

    31. Mai 2021 um 11:30 Uhr

  • Ich bin ein wenig verwirrt. Diese Frage haben Sie bereits mit der gleichen Paketempfehlung beantwortet, diesmal aber ohne Angabe Ihrer Zugehörigkeit!?

    – Emil Bergeron

    22. Juni 2021 um 22:30 Uhr

986780cookie-checkuseState set-Methode, die die Änderung nicht sofort widerspiegelt

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy