Я пытаюсь выучить хуки, и этот useState
метод меня запутал. Я присваиваю начальное значение состоянию в виде массива. У useState
меня метод set не работает даже с spread(...)
или without spread operator
. Я создал API на другом ПК, который я вызываю и получаю данные, которые я хочу установить в состояние.
Вот мой код:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
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);
setMovies(result);
console.log(movies);
} catch (e) {
console.error(e);
}
})();
}, []);
return <p>hello</p>;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
Как setMovies(result)
и setMovies(...result)
не работает. Здесь нужна помощь.
Я ожидаю, что переменная результата будет помещена в массив фильмов.
источник
useEffect
Возможно, это не лучшее решение, поскольку оно не поддерживает асинхронные вызовы. Итак, если мы хотим выполнить некоторую асинхронную проверкуmovies
изменения состояния, мы не можем это контролировать.the updater provided by useState hook
является ли он асинхронным, в отличие от того,this.state
что могло быть изменено, если быthis.setState
было синхронно, Замыкание вокругconst movies
останется таким же, даже еслиuseState
обеспечил синхронную функцию - см. пример в моем ответеsetMovies(prevMovies => ([...prevMovies, ...result]));
работал у меняДополнительные детали к предыдущему ответу :
Хотя РЕАКТ
setState
является асинхронной (оба класса и крючки), и это заманчиво использовать этот факт для объяснения наблюдаемого поведения, это не причина , почему это происходит.TL; DR: причина в закрытии области вокруг неизменяемого
const
значения.Решения:
прочтите значение в функции рендеринга (не внутри вложенных функций):
useEffect(() => { setMovies(result) }, []) console.log(movies)
добавить переменную в зависимости (и использовать правило eslint react -hooks / excustive -deps ):
useEffect(() => { setMovies(result) }, []) useEffect(() => { console.log(movies) }, [movies])
используйте изменяемую ссылку (когда это невозможно):
const moviesRef = useRef(initialValue) useEffect(() => { moviesRef.current = result console.log(moviesRef.current) }, [])
Объяснение, почему это происходит:
Если бы асинхронность была единственной причиной, это было бы возможно
await setState()
.Howerver предполагается, что оба
props
иstate
не меняются во время 1 рендеринга .С помощью хуков это предположение усиливается за счет использования постоянных значений с
const
ключевым словом:const [state, setState] = useState('initial')
Значение может отличаться для двух отрисовок, но остается постоянным внутри самого отрисовки и внутри любых замыканий (функции, которые живут дольше даже после завершения отрисовки, например
useEffect
, обработчики событий, внутри любого Promise или setTimeout).Рассмотрим следующую фальшивую, но синхронную реализацию , похожую на React:
// 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>
источник
Я только что закончил перезапись с помощью useReducer, следуя статье @kentcdobs (см. Ниже), которая действительно дала мне твердый результат, который не страдает от этих проблем с закрытием.
см .: https://kentcdodds.com/blog/how-to-use-react-context-effectively
Я сжал его читаемый шаблон до моего предпочтительного уровня СУХОСТИ - чтение его реализации в песочнице покажет вам, как это на самом деле работает.
Наслаждайтесь, я знаю !!
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, }
с использованием, подобным этому:
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 = {};
Теперь все плавно сохраняется на всех моих страницах
Ницца!
Спасибо, Кент!
источник
useEffect имеет собственное состояние / жизненный цикл, он не будет обновляться, пока вы не передадите функцию в параметрах или эффект не будет уничтожен.
объект и массив распространение или отдых не будут работать внутри 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]);
источник
// replace return <p>hello</p>; // with return <p>{JSON.stringify(movies)}</p>;
Теперь вы должны увидеть, что ваш код на самом деле делает работу. Что не работает, так это
console.log(movies)
. Это потому, чтоmovies
указывает на старое состояние . Если вы переместите свойconsole.log(movies)
за пределыuseEffect
, прямо над возвратом, вы увидите обновленный объект фильмов.источник