React устанавливает 2 состояния одновременно в useeffect

#reactjs

#reactjs

Вопрос:

У меня есть компонент, который я использую для отображения списка записей данных, подобных этому (упрощенный):

 // resource is the rest endpoint, 
// items is the parents components 
// state that holds the data entries and setItems is the corresponding setter
export default function LoadedList({resource,items, setItems,CustomFormat}){
    const [loadingOrError,setLoadingOrError] =useState(false)

    useEffect(()=>{
       axios.get(baseURL resource)
            .then((e)=>{
                setItems(e.data)
                setLoadingOrError(false)
            })
            .catch((e)=>{
                setItems([{text:"Error"}])
                setLoadingOrError(true)
            })
            setItems([{text:"Loading...-"}])
            setLoadingOrError(true)
    },[])
   
    return(
          <div className="list">
              {
                    items.map((item)=>
                        loadingOrError?
                             <DefaultFormat item={item} />
                        :
                             <CustomFormat item={item}/>
                    )
              }
          </div>
    )
}
 

Основная идея заключается в том, что пока компонент загружает элемент или в случае сбоя, для отображения соответствующего сообщения следует использовать формат по умолчанию.
После успешной загрузки элементов для форматирования записей следует использовать формат родительского элемента.
Проблема в том, что я обнаружил, что setItems и setLoading не изменяются одновременно. Похоже, что это работает так, что сначала он устанавливает элементы, затем повторно отправляет все записи и только затем изменяет loadingOrError на true. Итак, есть ли способ установить оба из них одновременно? Или просто без повторного воспроизведения всего, что находится между ними?

Ответ №1:

Вместо того, чтобы пытаться обновлять оба одновременно, почему бы вам не попробовать отслеживать состояние загрузки и ошибки отдельно, а затем сделать что-то вроде этого:

 // resource is the rest endpoint, 
// items is the parents components 
// state that holds the data entries and setItems is the corresponding setter
export default function LoadedList({resource, items, setItems, CustomFormat}){
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState("");

    useEffect(()=>{
       setLoading(true);
       axios.get(baseURL resource)
            .then((e)=>
                setItems(e.data)
            )
            .catch((e)=>
                setError("Error")
            )
            .finally(() => setLoading(false));
    },[])

    if(loading) {
      return "Loading ...";
    }

    if(error) {
      return error;
    }   

    return(
          <div className="list">
              {items.map((item, index) => <CustomFormat key={index} item={item}/>)}
          </div>
    )
}
 

Это должно отображаться Loading... до тех пор, пока не будут загружены все элементы.

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

 const [dataState, setDataState] = useState({
  data: null,
  loading: false,
  error: ""
});

...

setDataState({data: data, loading: false});
 

Помимо этого, я рекомендую две вещи:

  • Вы должны убедиться, что компонент все еще смонтирован после завершения запроса и непосредственно перед установкой состояния. В противном случае вы получите сообщение об ошибке. Этого очень просто добиться с помощью дополнительной переменной для отслеживания состояния монтирования.
  • Может быть полезно создать пользовательский хук для обработки запросов, поскольку это, вероятно, то, что вы будете делать часто, и в каждом случае это будет выглядеть очень похоже. Я нахожу пошаговое руководство в этом посте очень понятным.

Взято из этого сообщения:

Пользовательский хук useFetch

 import { useState, useEffect } from 'react';

const useFetch = (url = '', options = null) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    let isMounted = true;

    setLoading(true);

    fetch(url, options)
      .then(res => res.json())
      .then(data => {
        if (isMounted) {
          setData(data);
          setError(null);
        }
      })
      .catch(error => {
        if (isMounted) {
          setError(error);
          setData(null);
        }
      })
      .finally(() => isMounted amp;amp; setLoading(false));

    return () => (isMounted = false);
  }, [url, options]);

  return { loading, error, data };
};

export default useFetch;
 

Комментарии:

1. спасибо, это отвечает на мой вопрос. Я все еще считаю странным, что react настаивает на промежуточном рендеринге. Я также не знал, что мне нужно отслеживать состояние монтирования