
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.

Shubham Khatri
Ähnlich wie setState in Klassenkomponenten, die durch Erweitern erstellt wurden React.Component
oder React.PureComponent
die 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 die verwenden useEffect
Haken, ähnlich wie mit componentDidUpdate
in Klassenkomponenten, da der Setter von zurückgekehrt ist useState
hat kein Rückrufmuster
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]));

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 useEffect
Event-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>

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.useState
also 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:

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

Muhammad Imran Siddique
useEffect von React hat seinen eigenen Zustand/Lebenszyklus. Es hängt mit der Mutation des Zustands zusammen und 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 React-Hook sofort zu ändern.
const movies = React.useRef(null);
useEffect(() => {
movies.current="values";
console.log(movies.current)
}, [])
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

Peter Mortensen
Das fand ich gut. Anstatt den Zustand (Ansatz 1) als Beispiel zu definieren,
const initialValue = 1;
const [state,setState] = useState(initialValue)
Versuchen Sie diesen Ansatz (Ansatz 2),
const [state = initialValue,setState] = useState()
Dadurch wurde das Rerender-Problem ohne die Verwendung von useEffect behoben, da wir uns in diesem Fall nicht mit dem internen Schließungsansatz befassen.
PS: Wenn Sie den alten Zustand für einen beliebigen Anwendungsfall verwenden möchten, muss useState mit useEffect verwendet werden, da dieser Zustand benötigt wird, daher sollte in dieser Situation Ansatz 1 verwendet werden.
10055400cookie-checkDie set-Methode useState spiegelt eine Änderung nicht sofort wideryes
Können Sie sehen, wie sich die Änderungen bewegen?
console.log("movies =", movies);
außerhalb deruseEffect
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