metoda usestate set nie odzwierciedla natychmiastowej zmiany
Próbuję nauczyć się hooków i Metoda {[1] } sprawiła, że jestem zdezorientowany. Przypisuję wartość początkową do stanu w postaci tablicy. Metoda set w useState
nie działa dla mnie nawet z spread(...)
lub without spread operator
.
Zrobiłem API na innym komputerze, który wywołuję i pobieram dane, które chcę ustawić w stanie.
Oto Mój kod:
<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>
<div id="root"></div>
<script type="text/babel">
// 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>
setMovies(result)
jak również setMovies(...result)
nie działa. Przydałaby się pomoc.
Oczekuję zmienna wynikowa, która zostanie wepchnięta do tablicy filmów.
6 answers
Podobnie jak setState w komponentach klas utworzonych przez rozszerzenie React.Component
lub React.PureComponent
, Aktualizacja stanu przy użyciu Updater dostarczonego przez useState
hook jest również asynchroniczna i nie będzie natychmiast odzwierciedlana.
Ponadto, głównym problemem jest tu nie tylko asynchroniczny charakter, ale fakt, że wartości stanu są używane przez funkcje w oparciu o ich obecne zamknięcia, a aktualizacje stanu będą odzwierciedlać w następnym ponownym renderowaniu, przez które istniejące zamknięcia nie będą miały wpływu, ale nowe są utworzony . Teraz w bieżącym stanie wartości wewnątrz hooków są uzyskiwane przez istniejące zamknięcia, a gdy nastąpi ponowne renderowanie, zamknięcia są aktualizowane na podstawie tego, czy funkcja jest ponownie odtwarzana, czy nie.
Nawet jeśli dodasz setTimeout
funkcję, chociaż timeout będzie działał po pewnym czasie, w którym nastąpi ponowne renderowanie, setTimeout
nadal będzie używać wartości z poprzedniego zamknięcia, a nie zaktualizowanej.
setMovies(result);
console.log(movies) // movies here will not be updated
Jeśli chcesz wykonać akcję na stanie update, musisz użyć Hooka useEffect, podobnie jak użycie componentDidUpdate
w komponentach klasy, ponieważ setter zwracany przez useState nie ma wzorca wywołania zwrotnego
useEffect(() => {
// action on update of movies
}, [movies]);
Jeśli chodzi o składnię update state, setMovies(result)
zastąpi poprzednią wartość movies
w stanie tymi dostępnymi z żądania asynchronicznego.
Jednakże, jeśli chcesz scalić odpowiedź z wcześniej istniejącymi wartościami, musisz użyć składni wywołania zwrotnego state updation wraz z prawidłowym użyciem składnia rozproszona jak
setMovies(prevMovies => ([...prevMovies, ...result]));
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-29 12:08:28
dodatkowe informacje do poprzednia odpowiedź:
Podczas gdy React ' s setState
jest asynchroniczne (zarówno klasy, jak i Hooki), i kuszące jest wykorzystanie tego faktu do wyjaśnienia obserwowanego zachowania, nie jest to powód zdarza się.
TLDR: powodem jest zamknięcie zakres wokół niezmiennej const
wartości.
rozwiązania:
-
Odczyt wartości w render funkcja (nie wewnątrz funkcji zagnieżdżonych):
useEffect(() => { setMovies(result) }, []) console.log(movies)
-
Dodaj zmienną do Zależności (i użyj react-hooks/exhaustive-deps eslint rule):
useEffect(() => { setMovies(result) }, []) useEffect(() => { console.log(movies) }, [movies])
-
Użyj zmiennego odniesienia (gdy powyższe nie jest możliwe):
const moviesRef = useRef(initialValue) useEffect(() => { moviesRef.current = result console.log(moviesRef.current) }, [])
wyjaśnienie dlaczego tak się dzieje:
Gdyby asynchronizacja była jedynym powodem, byłoby możliwe await setState()
.
Howerver, zarówno props
jak i state
są zakłada się, że niezmienny podczas 1 renderowania .
Traktuj
this.state
jakby to było niezmienne.
Z hookami założenie to jest wzmocnione przez użycie wartości stałych ze słowem kluczowym const
:
const [state, setState] = useState('initial')
Wartość może być różna pomiędzy dwoma renderami, ale pozostaje stałą wewnątrz samego renderowania i wewnątrz dowolnych zamknięć (funkcje, które żyją dłużej nawet po zakończeniu renderowania, np. useEffect
, procedury obsługi zdarzeń, wewnątrz dowolnej obietnicy lub innych setTimeout).
Rozważ podążanie za fałszywą, ale synchroniczną , implementacją podobną do Reacta:
// 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>
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-02 15:36:01
Właśnie skończyłem przepisać z useReducer, po artykule @kentcdobs (ref poniżej), który naprawdę dał mi solidny wynik, który nie cierpi ani trochę z powodu tych problemów z zamknięciem.
Zobacz: https://kentcdodds.com/blog/how-to-use-react-context-effectively
Skondensowałem jego czytelną płytkę kotła do mojego preferowanego poziomu suchości-przeczytanie jego implementacji piaskownicy pokaże Ci, jak to naprawdę działa. Enjoy, I know I am !!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,
}
Z użyciem podobne do tego:
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 = {};
Teraz wszystko utrzymuje się płynnie wszędzie na wszystkich moich stronach
Nieźle! Dzięki Kent!Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-16 17:02:52
UseEffect ma swój własny stan / cykl życia, nie będzie aktualizowany, dopóki nie przekażesz funkcji w parametrach lub efekt zniszczony.
Object i array spread lub rest nie będą działać wewnątrz useEffect.
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]);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-04 06:19:25
Możesz go rozwiązać używając Hooka useRef
, ale wtedy nie będzie ponownie renderowany, gdy zostanie zaktualizowany. Stworzyłem Hooki o nazwie useStateRef, które dają ci dobro z obu światów. To jest jak stan, który po aktualizacji komponent ponownie renderuje i jest jak "ref", który zawsze ma najnowszą wartość.
Zobacz ten przykład:
var [state,setState,ref]=useStateRef(0)
Działa dokładnie tak jak useState
, ale dodatkowo Podaje aktualny stan pod ref.current
Ucz się więcej:
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-24 09:28:15
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;
Teraz powinieneś zobaczyć, że Twój kod faktycznie działa . Nie działa console.log(movies)
. Dzieje się tak dlatego, że movies
wskazuje na stary stan . Jeśli przeniesiesz console.log(movies)
poza useEffect
, tuż nad return, zobaczysz obiekt zaktualizowane filmy.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-18 06:49:22