useState в React Hooks не обновляет массив

Я пробовал много вещей и, кажется, не могу понять, почему setTypes не обновляет массив типов?

import { useState, useEffect } from 'react';
import { PostList } from './post-list';
import * as api from '../utils/api';

export const PostSelector = (props) => {
  const [posts, setPosts]     = useState([]);
  const [loading, setLoading] = useState(false);
  const [type, setType]       = useState('post');
  const [types, setTypes]     = useState([]);
  
  const fetchTypes = async () => {
    setLoading(true);
    const response = await api.getPostTypes();
    delete response.data.attachment;
    delete response.data.wp_block;
    const postTypes = response.data;
    console.log(response.data); // {post: {…}, page: {…}, case: {…}}
    setTypes(postTypes);
    console.log(types); // []

    // Why types remain empty??
  }

const loadPosts = async (args = {}) => {
  const defaultArgs = { per_page: 10, type };
  const requestArgs = { ...defaultArgs, ...args };
  

  requestArgs.restBase = types[requestArgs.type].rest_base; // Cannot read property 'rest_base' of undefined
  
  const response = await api.getPosts(requestArgs);
  console.log(response.data);
}

useEffect(() => {
  fetchTypes();
  loadPosts();
}, []);

  return (
    <div className="filter">
      <label htmlFor="options">Post Type: </label>
      <select name="options" id="options">
        { types.length < 1 ? (<option value="">loading</option>) : Object.keys(types).map((key, index) => <option key={ index } value={ key }>{ types[key].name }</option> ) }
      </select>
    </div>
  );
}

Пожалуйста, взгляните на console.log и обратите внимание на разные ответы.

Я пытаюсь загрузить список типов, в данном случае «сообщение», «страница» и «дело», а затем отобразить список сообщений на основе текущего «типа». Тип по умолчанию - «сообщение».

Если я добавлю [типы] в useEffect. Я наконец получил значения, но компонент отрисовывает без остановок.

Спасибо всем за комментарии. Многие люди указали на проблему, заключающуюся в том, что тот факт, что мы устанавливаем состояние, не означает, что оно будет установлено сразу, потому что оно асинхронное.

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


person drjorgepolanco    schedule 19.02.2020    source источник
comment
Попробуйте использовать оператор распространения. setTypes([...postTypes]);   -  person boosted_duck    schedule 19.02.2020
comment
Спасибо за быстрый ответ, но результат тот же.   -  person drjorgepolanco    schedule 19.02.2020
comment
похоже, что response.data - это объект, а не массив   -  person Glen K    schedule 19.02.2020
comment
Я не уверен, что setTypes() асинхронный метод. Но попробуйте использовать await, await setTypes([...postTypes]);   -  person boosted_duck    schedule 19.02.2020
comment
У вас нет ошибок на console.log? Если вы их получите, это также будет объединено с вопросом, чтобы опубликовать их.   -  person Alexander Santos    schedule 19.02.2020
comment
Спасибо @GlenK. Я попытался добавить объект в качестве значения типов по умолчанию, просто чтобы посмотреть, обновит ли это состояние, все равно не сработает. Вместо пустого массива возвращает пустой объект.   -  person drjorgepolanco    schedule 19.02.2020
comment
Спасибо @AlexanderSantos. Но ошибок нет.   -  person drjorgepolanco    schedule 19.02.2020
comment
Спасибо, @boosted_duck попробовал с ожиданием, но с оператором распространения и без него, все тот же точный результат.   -  person drjorgepolanco    schedule 19.02.2020
comment
Отвечает ли это на ваш вопрос? метод useState не сразу отражает изменение   -  person segFault    schedule 19.02.2020
comment
Это ответ на ваш вопрос. stackoverflow.com/questions/60294328/   -  person Zain Ul Abideen    schedule 19.02.2020
comment
Спасибо, @ZainUlAbideen! Проблема в этом случае в том, что типы - это объект, поэтому useEffect будет повторно отрисовываться навсегда.   -  person drjorgepolanco    schedule 19.02.2020
comment
Добро пожаловать, но я не понимаю, как повторный рендеринг связан с типами, являющимися объектами?   -  person Zain Ul Abideen    schedule 19.02.2020
comment
@ZainUlAbideen взгляните на это: dev.to/ karthick3018 /   -  person drjorgepolanco    schedule 19.02.2020
comment
@drjorgepolanco, Спасибо, надеюсь, вы прочитали комментарии к этой статье;) Рад, что вам помогли. Удачного кодирования.   -  person Zain Ul Abideen    schedule 20.02.2020


Ответы (5)


Похоже, что useState является асинхронным и не обновляет значение сразу после его вызова.

Просмотрите этот же случай здесь

person nicoavn    schedule 19.02.2020

SetTypes useState - это асинхронная функция, поэтому изменения не вступают в силу немедленно. Вы можете использовать useEffect, чтобы проверить, не изменилось ли что-нибудь

useEffect(()=>{
    const defaultArgs = { per_page: 10, type };
    const requestArgs = { ...defaultArgs, ...args };
    requestArgs.restBase = types;
    console.log("types updated",types)
},[types])

Вы можете удалить loadPosts, потому что теперь useEffect будет запускаться при изменении типов

person Muhammad Abdullah    schedule 19.02.2020
comment
Если я передаю [types], рендеринг никогда не прекращается. - person drjorgepolanco; 19.02.2020
comment
Это странно. Не могли бы вы предоставить пример кода на stackblitz.com/edit/react-leukwv, чтобы я мог протестировать Это. - person Muhammad Abdullah; 19.02.2020

Вы объявили свои типы массивом, но передаете ему словарь словарей. Попробуй это:

const [types, setTypes]     = useState({});

Вам также не нужно звонить

loadPosts()

потому что хук useState повторно отобразит ваш компонент, обновив только то, что необходимо.

person PurpSchurp    schedule 19.02.2020

Для любого, кто приходит сюда и не может установить / обновить массив useState, вам необходимо использовать оператор распространения (...), а не только массив, например. [... initState] вместо initState

 //initialise
  const initState: boolean[] = new Array(data.length).fill(false);
  const [showTable, setShowTable] = useState<boolean[]>([...initState]);

  // called from an onclick to update
  const updateArray = (index: number) => {
    showTable[index] = !showTable[index];
    setShowTable([...showTable]);
  };
person 72GM    schedule 18.05.2021

Хорошо, короткий ответ связан с закрытием

Это не из-за asynchronous, как сказано в других ответах !!!


Решение (☞ ゚ ヮ ゚) ☞

Вы можете проверить изменения с помощью console.log в return такой функции.

return (
    <div> Hello World! 
      {
        console.log(value) // this will reference every re-render
      } 
    </div>
  );

или создайте новый useEffect с value в качестве зависимости, как показано ниже

 React.useEffect(() => {
    console.log(value); // this will reference every value is changed
  }, [value]);

function App() {
  const [value, Setvalue] = React.useState([]);
  
  React.useEffect(() => {
    Setvalue([1, 2, 3]);
    console.log(value); // this will reference to value at first time
  }, []);
  
  return (
    <div> Hello World! 
      {
        console.log(value) // this will reference every re-render
      } 
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

<div id="app"></div>

Подробнее читайте здесь: метод useState не сразу отражает изменение

person Nguyễn Văn Phong    schedule 18.05.2021
comment
Отвечает ли это на ваш вопрос? Дайте мне знать, если вам понадобится помощь ^^! @drjorgepolanco - person Nguyễn Văn Phong; 25.05.2021