8

Метод set в useState не отражает изменения немедленно

11

Я пытаюсь изучить хуки в 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 ответ(ов)

0

Как уже было разъяснено в других ответах, ошибка в том, что useState работает асинхронно, и вы пытаетесь использовать новое значение сразу после вызова setState. Оно не обновляется в части с console.log() из-за асинхронной природы setState: этот метод позволяет вашему коду продолжать выполнение, в то время как обновление значения происходит в фоновом режиме. Таким образом, вы получаете предыдущее значение. Когда асинхронное обновление setState завершается, значение обновляется, и у вас будет доступ к нему при следующем рендере.

Если кого-то интересует более подробное объяснение этой темы, вот отличная конференция на эту тему:

https://www.youtube.com/watch?v=8aGhZQkoFbQ

0

В 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);
}, []);

Такой подход позволяет сохранять данные, которые не требуют повторного рендеринга компонента.

0

Ваше решение с использованием useReducer, вдохновленное материалом Кента К. Додда, действительно выглядит очень достойно. Уменьшение проблем с замыканиями — важный шаг для обеспечения удобочитаемости и предсказуемости вашего кода. Давайте разберем ваш подход.

Вы создали контексты для управления состоянием приложения и диспетчера, что позволяет выделить логику управления состоянием в одном месте. Ваша реализация редюсера stateReducer также выглядит довольно элегантно и позволяет обрабатывать различные действия, обновляя только те свойства состояния, которые изменились.

Вот несколько ключевых моментов, которые стоит отметить:

  1. Избавление от замыканий: Благодаря использованию useReducer и контекстов, вы минимизируете возможность возникновения проблем с замыканиями, так как состояние и функции-диспетчеры передаются через контекст, а не сохраняются в локальных замыканиях.

  2. DRY-принцип: Подход с использованием функции useDispatchable, реализующий абстракцию над контекстами, помогает избежать дублирования кода для каждого отдельного состояния, что делает ваш код более чистым и поддерживаемым.

  3. Легкость в использовании компонентов: В компоненте AltIdPage вы удобно используете хук useKeyCode и другие хуки для работы с состоянием вашего приложения, что делает ваш компонент чистым и сосредоточенным на логике рендеринга.

  4. Удобная структура: Четкая структура кода, которая разделяет контексты, редюсеры и компоненты, делает ваше приложение более понятным для других разработчиков и облегчает дальнейшую поддержку.

Как итог, ваше решение позволяет сохранять состояние модульным, предсказуемым и легким в поддержке. Если у вас есть вопросы или если вы хотели бы обсудить некоторые аспекты вашего кода более подробно, не стесняйтесь задавать вопросы!

0

Закрытие - это не единственная причина.

Судя по исходному коду 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

0

Я нашёл это решение хорошим. Вместо того, чтобы определять состояние (подход 1) следующим образом:

const initialValue = 1;
const [state, setState] = useState(initialValue);

Попробуйте этот подход (подход 2):

const [state = initialValue, setState] = useState();

Это решает проблему повторного рендеринга без использования useEffect, так как в данном случае нас не интересует внутренний подход замыкания.

P.S.: Если вас беспокоит использование старого состояния для каких-либо случаев, тогда необходимо использовать useState в сочетании с useEffect, так как потребуется это состояние, поэтому подход 1 следует использовать в этой ситуации.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь