Метод set в useState не отражает изменения немедленно
Я пытаюсь изучить хуки в React, и метод 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; // вариант для веб-браузера
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>
Ни setMovies(result)
, ни setMovies(...result)
не работают.
Я ожидаю, что переменная result
будет добавлена в массив movies
.
5 ответ(ов)
Как уже было разъяснено в других ответах, ошибка в том, что useState
работает асинхронно, и вы пытаетесь использовать новое значение сразу после вызова setState
. Оно не обновляется в части с console.log()
из-за асинхронной природы setState
: этот метод позволяет вашему коду продолжать выполнение, в то время как обновление значения происходит в фоновом режиме. Таким образом, вы получаете предыдущее значение. Когда асинхронное обновление setState
завершается, значение обновляется, и у вас будет доступ к нему при следующем рендере.
Если кого-то интересует более подробное объяснение этой темы, вот отличная конференция на эту тему:
В React
, хук 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 {}
})();
}, [setData]);
Или можно использовать Promise.all
для одновременного получения данных:
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]) => {
setData({
countries: country,
projects: project,
regions: region
});
});
} catch {
console.log("Ошибка получения данных");
}
})();
}, [setData]);
В качестве альтернативы, вы можете использовать React.useRef()
для мгновенного изменения в хуке React:
const movies = React.useRef(null);
useEffect(() => {
movies.current = 'values';
console.log(movies.current);
}, []);
Такой подход позволяет сохранять данные, которые не требуют повторного рендеринга компонента.
Ваше решение с использованием useReducer
, вдохновленное материалом Кента К. Додда, действительно выглядит очень достойно. Уменьшение проблем с замыканиями — важный шаг для обеспечения удобочитаемости и предсказуемости вашего кода. Давайте разберем ваш подход.
Вы создали контексты для управления состоянием приложения и диспетчера, что позволяет выделить логику управления состоянием в одном месте. Ваша реализация редюсера stateReducer
также выглядит довольно элегантно и позволяет обрабатывать различные действия, обновляя только те свойства состояния, которые изменились.
Вот несколько ключевых моментов, которые стоит отметить:
Избавление от замыканий: Благодаря использованию
useReducer
и контекстов, вы минимизируете возможность возникновения проблем с замыканиями, так как состояние и функции-диспетчеры передаются через контекст, а не сохраняются в локальных замыканиях.DRY-принцип: Подход с использованием функции
useDispatchable
, реализующий абстракцию над контекстами, помогает избежать дублирования кода для каждого отдельного состояния, что делает ваш код более чистым и поддерживаемым.Легкость в использовании компонентов: В компоненте
AltIdPage
вы удобно используете хукuseKeyCode
и другие хуки для работы с состоянием вашего приложения, что делает ваш компонент чистым и сосредоточенным на логике рендеринга.Удобная структура: Четкая структура кода, которая разделяет контексты, редюсеры и компоненты, делает ваше приложение более понятным для других разработчиков и облегчает дальнейшую поддержку.
Как итог, ваше решение позволяет сохранять состояние модульным, предсказуемым и легким в поддержке. Если у вас есть вопросы или если вы хотели бы обсудить некоторые аспекты вашего кода более подробно, не стесняйтесь задавать вопросы!
Закрытие - это не единственная причина.
Судя по исходному коду useState
(упрощенному ниже), становится очевидно, что значение никогда не присваивается сразу.
На самом деле, когда вы вызываете setValue
, действие обновления ставится в очередь. И только когда стартует планировщик и наступает следующий рендер, это действие обновления применяется к состоянию.
Это означает, что даже если у нас нет проблемы с закрытием, версия React для 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 В ЗДЕСЬ
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
Я нашёл это решение хорошим. Вместо того, чтобы определять состояние (подход 1) следующим образом:
const initialValue = 1;
const [state, setState] = useState(initialValue);
Попробуйте этот подход (подход 2):
const [state = initialValue, setState] = useState();
Это решает проблему повторного рендеринга без использования useEffect, так как в данном случае нас не интересует внутренний подход замыкания.
P.S.: Если вас беспокоит использование старого состояния для каких-либо случаев, тогда необходимо использовать useState в сочетании с useEffect, так как потребуется это состояние, поэтому подход 1 следует использовать в этой ситуации.
Как программно выполнять навигацию с помощью React Router?
Как условно добавлять атрибуты к компонентам React?
Как установить фокус на поле ввода после рендеринга?
Ошибка в ReactJS: Компонент изменяет неконтролируемый текстовый ввод на контролируемый
В чем разница между React Native и React?