Я пытаюсь выучить хуки, и метод useState сбил меня с толку. Я присваиваю начальное значение состоянию в виде массива. Метод установки в useState у меня не работает ни с оператором спреда, ни без него.
Я создал API на другом ПК, который я вызываю и получаю данные, которые я хочу установить в состояние.
Вот мой код:
<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.info("result = ", result);
setMovies(result);
console.info("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>Ни setMovies(result), ни setMovies(...result) не работают.
Я ожидаю, что переменная результата будет помещена в массив фильмов.
Он используется как пример в мета-вопрос.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Подобно setState в компонентах класса, созданных расширением React.Component или React.PureComponent, обновление состояния с помощью средства обновления, предоставляемого ловушкой useState, также является асинхронным и не будет отражено немедленно.
Кроме того, основная проблема здесь не только в асинхронном характере, но и в том, что значения состояния используются функциями на основе их текущих закрытий, а обновления состояния будут отражаться в следующем повторном рендеринге, при котором существующие закрытия не будут затронуты, но будут добавлены новые. созданы. Теперь в текущем состоянии значения внутри хуков получаются существующими замыканиями, и когда происходит повторный рендеринг, замыкания обновляются в зависимости от того, воссоздана ли функция снова или нет.
Даже если вы добавите функцию setTimeout, хотя по истечении некоторого времени, по истечении которого произойдет повторный рендеринг, пройдет тайм-аут, setTimeout все равно будет использовать значение из своего предыдущего закрытия, а не обновленное.
setMovies(result);
console.info(movies) // movies here will not be updated
Если вы хотите выполнить действие при обновлении состояния, вам необходимо использовать ловушку useEffect, как при использовании componentDidUpdate в компонентах класса, поскольку установщик, возвращаемый useState, не имеет шаблона обратного вызова
useEffect(() => {
// action on update of movies
}, [movies]);
Что касается синтаксиса для обновления состояния, setMovies(result) заменит предыдущее значение movies в состоянии на значения, доступные из асинхронного запроса.
Однако, если вы хотите объединить ответ с ранее существовавшими значениями, вы должны использовать синтаксис обратного вызова обновления состояния вместе с правильным использованием синтаксиса распространения, например
setMovies(prevMovies => ([...prevMovies, ...result]));
Привет, а как насчет вызова useState внутри обработчика отправки формы? Я работаю над проверкой сложной формы, и я вызываю внутри submitHandler ловушки useState, и, к сожалению, изменения происходят не сразу!
useEffect может быть не лучшим решением, поскольку он не поддерживает асинхронные вызовы. Итак, если мы хотим выполнить некоторую асинхронную проверку изменения состояния movies, мы не можем это контролировать.
обратите внимание, что хотя совет очень хорош, объяснение причины может быть улучшено - ничего общего с тем фактом, является ли the updater provided by useState hook асинхронным, в отличие от this.state, который мог бы быть мутирован, если бы this.setState был синхронным, замыкание вокруг const movies останется то же самое, даже если useState обеспечивал синхронную функцию - см. пример в моем ответе
setMovies(prevMovies => ([...prevMovies, ...result])); у меня работал
Он регистрирует неправильный результат, потому что вы регистрируете устаревшее закрытие не потому, что установщик является асинхронным. Если проблема была в асинхронном режиме, вы могли войти в систему после тайм-аута, но вы могли бы установить тайм-аут на час и по-прежнему регистрировать неправильный результат, потому что асинхронность не является причиной проблемы.
@HMR Я понимаю, о чем вы говорите. Возможно, я не объяснил это должным образом в своем посте. Я ведь писал, что значение будет обновлено только при следующем рендере. Я обновлю свой псот
@ShubhamKhatri, эй, я использую способ распространения, чтобы объединить предыдущий с новым ответом, но он не работает для меня, вот основной вопрос, вы можете проверить его?
@ShubhamKhatri Что, если это не объект ценностей. только одно значение
const [timeSlotList, setTimeSlotList] = useState(""); Это работает для меня .... `setTimeSlotList(() => [...timeSlotTime]); @ da45 Я обнаружил, что клонирование предыдущего состояния в ловушке setState исправило это. Как в приведенном выше примере.
Так что же делать, если вам нужно немедленное изменение? Например, когда я нажимаю кнопку «Отправить», мне нужно немедленно показать счетчик, а не ждать setLoading, а затем проверять загрузку при рендеринге.
@geneb. переход между состояниями не будет медленным. На самом деле его даже не видно невооруженным глазом.
Привет, а как насчет того, чтобы передать сюда параметры обратного вызова? useEffect(() => { // action on update of movies }, [movies]);
Спасибо за ответ! Это было очень четкое объяснение !.
Есть простой способ сделать это в обработчике отправки формы (или любом обработчике событий) stackoverflow.com/a/70405577/5823517 без useEffect
Спасибо Уважаемый, вы спасаете мне день !!!!
Additional details to the previous answer:
Хотя React setStateявляется асинхронен (и классы, и хуки), и заманчиво использовать этот факт для объяснения наблюдаемого поведения, это не Причина, почему, это случается.
TL; DR: причина в области закрытие вокруг неизменного значения const.
прочтите значение в функции рендеринга (не внутри вложенных функций):
useEffect(() => { setMovies(result) }, [])
console.info(movies)
добавить переменную в зависимости (и использовать правило eslint реагировать-хуки / исчерпывающие-депс):
useEffect(() => { setMovies(result) }, [])
useEffect(() => { console.info(movies) }, [movies])
используйте временную переменную:
useEffect(() => {
const newMovies = result
console.info(newMovies)
setMovies(newMovies)
}, [])
используйте изменяемую ссылку (если нам не нужно состояние и мы хотим запомнить только значение - обновление ссылки не вызывает повторного рендеринга):
const moviesRef = useRef(initialValue)
useEffect(() => {
moviesRef.current = result
console.info(moviesRef.current)
}, [])
Если бы асинхронность была единственной причиной, возможно, await setState().
Однако и props, и state являются предполагается, что не меняется в течение 1 рендера.
Treat
this.stateas if it were immutable.
С помощью хуков это предположение усиливается за счет использования постоянные значения с ключевым словом 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.info('in render:', x) // ✅
const handleClick = () => {
setX(current => current + 1)
console.info('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>Отличный ответ. ИМО, этот ответ убережет вас от горя в будущем, если вы потратите достаточно времени, чтобы прочитать и усвоить его.
@AlJoslin на первый взгляд кажется отдельной проблемой, даже если это может быть вызвано закрытием области видимости. Если у вас есть конкретный вопрос, создайте новый вопрос о stackoverflow с примером кода и всем ...
на самом деле я только что закончил перезапись с помощью useReducer, следуя статье @kentcdobs (см. ниже), которая действительно дала мне твердый результат, который не страдает от этих проблем с закрытием. (ссылка: kentcdodds.com/blog/how-to-use-react-context-effectively)
по какой-то причине решение 2 не работает .. Я получаю обратный вызов, но значение все еще пустое. useEffect(() => { console.info(movies) }, [movies]) ничего не печатает ..
@ACV Решение 2 отлично подходит для исходного вопроса. Если вам нужно решить другую проблему, YMMW, но я все еще на 100% уверен, что приведенный код работает так, как задокументировано, и проблема где-то в другом месте.
Это должен был быть ответ «Принято» @Pranjal
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;
Теперь вы должны увидеть, что ваш код на самом деле делает работает. Что не работает, так это console.info(movies). Это потому, что movies указывает на старое состояние. Если вы переместите console.info(movies) за пределы useEffect, прямо над возвратом вы увидите обновленный объект фильмов.
Не уверен, почему этот ответ получил большое количество голосов, в нем рассказывается, как получить «ожидаемое» значение console.info, поместив его вне функции useState. Просто и мило, если кто-то хочет знать, почему это происходит именно так, можно обратиться к подробным разъяснениям выше.
Я только что закончил переписывание с помощью 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 = {};
Теперь все плавно сохраняется везде на всех моих страницах
Я не думаю, что этот ответ особенно полезен в контексте ОП. В этом ответе даже не используется useState(), который занимал центральное место в запросе ОП.
Плавное решение, но не ответ на происходящее
React useEffect имеет собственное состояние / жизненный цикл, связанный с изменением состояния, он не будет обновлять состояние до тех пор, пока эффект не будет уничтожен.
Просто передайте один аргумент в параметре state или оставьте черный массив, и он будет работать отлично.
React.useEffect(() => {
console.info("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.info(country, project, region);
setData({
countries: country,
projects: project,
regions: region
});
})
} catch {
console.info("data fetch error")
}
})()
}, [setData]);
В качестве альтернативы вы можете попробовать React.useRef () для мгновенного изменения хука реакции.
const movies = React.useRef(null);
useEffect(() => {
movies.current='values';
console.info(movies.current)
}, [])
В последнем примере кода нет необходимости ни в async, ни в ожидании, поскольку вы используете Promise API. Это нужно только в первую
чувак, ты только что в полночь спас меня! Большое спасибо @muhammad
Я знаю, что уже есть очень хорошие ответы. Но я хочу дать другую идею, как решить ту же проблему и получить доступ к последнему состоянию «фильма», используя мой модуль реагировать-useStateRef.
Как вы понимаете, используя состояние React, вы можете отображать страницу при каждом изменении состояния. Но используя React ref, вы всегда можете получить самые свежие значения.
Таким образом, модуль react-useStateRef позволяет вам использовать состояния и ссылки вместе. Он обратно совместим с React.useState, поэтому вы можете просто заменить инструкцию import
const { useEffect } = React
import { useState } from 'react-usestateref'
const [movies, setMovies] = useState(initialValue);
useEffect(() => {
(async function() {
try {
const result = [
{
id: "1546514491119",
},
];
console.info("result = ", result);
setMovies(result);
console.info("movies = ", movies.current); // will give you the latest results
} catch (e) {
console.error(e);
}
})();
}, []);
Есть много хороших ответов, которые показывают, как исправить ваш код, но есть пакет NPM, который позволяет вам исправить это, просто изменив import. Это называется реагировать-useStateRef.
В твоем случае:
import useState from 'react-usestateref'
const [movies, setMovies,moviesRef] = useState(initialValue);
....
useEffect(() => {
setMovies(...)
console.info(moviesRef.current) // It will have the last value
})
Как видите, использование этой библиотеки позволит вам получить доступ к последнему состоянию.
Не работает Я установил и импортировал useStateRef. но никаких изменений в этой проблеме. const [pageCountOwnAlbum, setpageCountOwnAlbum, refOwnAlbum] = useStateRef (1) // Разбивка на страницы собственных данных альбома Получение console.info ("Value ---", refOwnAlbum.current);
Даёт то же, что и предыдущий
Я немного запутался. Вы ответили на этот вопрос уже той же рекомендацией по пакету, но на этот раз не раскрываете свою принадлежность !?
Я нашел, что это хорошо. Вместо определения состояния (подход 1), например,
const initialValue = 1;
const [state,setState] = useState(initialValue)
Попробуйте этот подход (подход 2),
const [state = initialValue,setState] = useState()
Это решило проблему повторного рендеринга без использования useEffect, поскольку в этом случае нас не интересует его подход к внутреннему закрытию.
P.S .: Если вас беспокоит использование старого состояния для любого варианта использования, необходимо использовать useState с useEffect, так как оно должно иметь это состояние, поэтому в этой ситуации следует использовать подход 1.
Для более удобной работы с обещаниями существует специальный синтаксис, который называется «async / await». Его на удивление легко понять и использовать.
В этом случае с setState может быть несколько решений. Одно из самых простых и удобных решений, которое я нашел, выглядит следующим образом:
В функциональном компоненте используйте useEffect () как async / await. То есть здесь, в приведенном выше примере, useEffect () уже реализована как асинхронная функция. Теперь сделайте setMovies (results) как await, например:
await setMovies(results);
Это обязательно решит проблему немедленной смены. Ключевое слово Ждите заставляет JavaScript ждать, пока это обещание не будет выполнено, и вернет свой результат.
Вам также не нужно устанавливать начальное значение, как написано в вопросе.
Вы можете объявлять только фильмы с переменными параметрами, например
const [movies, setMovies] = useState([]);
Для получения дополнительной информации см. Асинхронный / ожидание.
setMovies не возвращает обещание, поэтому async/await на самом деле ничего не исправит. Может показаться, что это работает, но на самом деле это простое условие гонки.
С настраиваемыми хуками из моей библиотеки вы можете дождаться обновления значений состояния:
useAsyncWatcher(...values):watcherFn(peekPrevValue: boolean)=>Promise - это оболочка обещания вокруг useEffect, которая может ждать обновлений и возвращать новое значение и, возможно, предыдущее, если для необязательного аргумента peekPrevValue установлено значение true. import React, { useState, useEffect, useCallback } from "react";
import { useAsyncWatcher } from "use-async-effect2";
function TestComponent(props) {
const [counter, setCounter] = useState(0);
const [text, setText] = useState("");
const textWatcher = useAsyncWatcher(text);
useEffect(() => {
setText(`Counter: ${counter}`);
}, [counter]);
const inc = useCallback(() => {
(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
setCounter((counter) => counter + 1);
const updatedText = await textWatcher();
console.info(updatedText);
})();
}, []);
return (
<div className = "component">
<div className = "caption">useAsyncEffect demo</div>
<div>{counter}</div>
<button onClick = {inc}>Inc counter</button>
</div>
);
}
export default TestComponent;
useAsyncDeepState - это реализация с глубоким состоянием (похожая на this.setState (patchObject)), сеттер которой может возвращать обещание, синхронизированное с внутренним эффектом. Если установщик вызывается без аргументов, он не изменяет значения состояния, а просто подписывается на обновления состояния. В этом случае вы можете получить значение состояния из любого места внутри вашего компонента, поскольку закрытие функций больше не является помехой.import React, { useCallback, useEffect } from "react";
import { useAsyncDeepState } from "use-async-effect2";
function TestComponent(props) {
const [state, setState] = useAsyncDeepState({
counter: 0,
computedCounter: 0
});
useEffect(() => {
setState(({ counter }) => ({
computedCounter: counter * 2
}));
}, [state.counter]);
const inc = useCallback(() => {
(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
await setState(({ counter }) => ({ counter: counter + 1 }));
console.info("computedCounter = ", state.computedCounter);
})();
});
return (
<div className = "component">
<div className = "caption">useAsyncDeepState demo</div>
<div>state.counter : {state.counter}</div>
<div>state.computedCounter : {state.computedCounter}</div>
<button onClick = {() => inc()}>Inc counter</button>
</div>
);
}
Нашел хитрость в перехвате setState. Вы не должны использовать старую переменную. Вы должны создать новую переменную и передать ее на перехватчик. Например:
const [users, setUsers] = useState(['Ayşe', 'Fatma'])
useEffect(() => {
setUsers((oldUsers) => {
oldUsers.push(<div>Emir</div>)
oldUsers.push(<div>Buğra</div>)
oldUsers.push(<div>Emre</div>)
return oldUsers
})
}, [])
return (
<Fragment>
{users}
</Fragment>
)
Вы увидите только пользователей Ayşe и Fatma. Потому что вы возвращаете (или передаете) переменную oldUsers. Эта переменная имеет ТАКУЮ ССЫЛКУ со ссылкой на старое состояние. Вы должны вернуть НОВОЕ СОЗДАННУЮ переменную. Если вы передадите ту же указанную переменную, тогда Reactjs не обновит состояние.
const [users, setUsers] = useState(['Ayşe', 'Fatma'])
useEffect(() => {
setUsers((oldUsers) => {
const newUsers = [] // Create new array. This is so important.
// you must push every old item to our newly created array
oldUsers.map((user, index) => {
newUsers.push(user)
})
// NOTE: map() function is synchronous
newUsers.push(<div>Emir</div>)
newUsers.push(<div>Buğra</div>)
newUsers.push(<div>Emre</div>)
return newUsers
})
}, [])
return (
<Fragment>
{users}
</Fragment>
)
Это не связанная с этим проблема, хотя в вопросе OP он в конечном итоге обновляет состояние просто в более поздних циклах рендеринга. Обновление массива состояний связано с собственными проблемами, которые уже решены в Stack Overflow.
var [state,setState]=useState(defaultValue)
useEffect(()=>{
var updatedState
setState(currentState=>{ // Do not change the state by get the updated state
updateState=currentState
return currentState
})
alert(updateState) // the current state.
})
Используйте библиотеку Таймер фона. Это решило мою проблему.
const timeoutId = BackgroundTimer.setTimeout(() => {
// This will be executed once after 1 seconds
// even when the application is the background
console.info('tac');
}, 1000);
Закрытие - не единственная причина.
На основе исходного кода useState (упрощенного ниже). Мне кажется, значение никогда не назначается сразу.
Что происходит, так это то, что действие обновления ставится в очередь, когда вы вызываете setValue. И после того, как расписание сработает, и только когда вы перейдете к следующему рендерингу, это действие обновления применяется к этому состоянию.
Это означает, что даже у нас нет проблемы с закрытием, реагирующая версия useState не даст вам сразу новое значение. Новое значение даже не существует до следующего рендеринга.
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();
}
Также есть статья, объясняющая вышесказанное аналогичным образом, https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8
Хороший анализ. Вааай, слишком редко люди читают источник
Если нам нужно только обновить состояние, то лучшим способом будет использование для этого метода push.
Вот мой код. Я хочу сохранить URL-адреса из Firebase в состоянии.
const [imageUrl, setImageUrl] = useState([]);
const [reload, setReload] = useState(0);
useEffect(() => {
if (reload === 4) {
downloadUrl1();
}
}, [reload]);
const downloadUrl = async () => {
setImages([]);
try {
for (let i = 0; i < images.length; i++) {
let url = await storage().ref(urls[i].path).getDownloadURL();
imageUrl.push(url);
setImageUrl([...imageUrl]);
console.info(url, 'check', urls.length, 'length', imageUrl.length);
}
}
catch (e) {
console.info(e);
}
};
const handleSubmit = async () => {
setReload(4);
await downloadUrl();
console.info(imageUrl);
console.info('post submitted');
};
Этот код работает, чтобы поместить URL-адреса в состояние в виде массива. Это также может сработать для вас.
Реализовать обратный вызов установки состояния (без useEffect) в рамках функции установщика несложно.
Используйте обещание для разрешения нового состояния в теле функции установки:
const getState = <T>(
setState: React.Dispatch<React.SetStateAction<T>>
): Promise<T> => {
return new Promise((resolve) => {
setState((currentState: T) => {
resolve(currentState);
return currentState;
});
});
};
В этом примере показано сравнение count и outOfSyncCount / syncCount в рендеринге пользовательского интерфейса:
const App: React.FC = () => {
const [count, setCount] = useState(0);
const [outOfSyncCount, setOutOfSyncCount] = useState(0);
const [syncCount, setSyncCount] = useState(0);
const handleOnClick = async () => {
setCount(count + 1);
setOutOfSyncCount(count);
const newCount = await getState(setCount);
setSyncCount(newCount);
// Or then'able
// getState(setCount).then(setSyncCount);
};
return (
<>
<h2>Count = {count}</h2>
<h2>Synced count = {syncCount}</h2>
<h2>Out of sync count = {outOfSyncCount}</h2>
<button onClick = {handleOnClick}>Increment</button>
</>
);
};
Я думаю, что привлекательный вариант использования этого подхода - это контекстный api и асинхронные и / или побочные эффекты, которые очень похожи на преобразователи Redux. Это также потребует меньшей сложности, чем Метод Кента. (Я не использовал это в больших примерах или производстве, поэтому я не знаю, может быть, вы можете попасть в какие-то странные состояния).
Пример:
// Sample "thunk" with async and side effects
const handleSubmit = async (
setState: React.Dispatch<SetStateAction<FormState>>
): Promise<void> => {
const state = await getState(setState);
if (!state.validated) {
return;
}
// Submit endpoint
await fetch('submitFormUrl', { method: 'post', body: state });
tackFormSuccess(state);
setState({ ...state, submitted: true });
};
// Component consuming the thunk
const SubmitComponent: React.FC = () => {
const dispatch = useFormDispatch();
return <button onclick = {() => handleSubmit(dispatch)}>Submit</button>
}
Вы видите изменения, перемещающие
console.info("movies = ", movies);за пределы ловушкиuseEffect?