Допустим, у меня есть компонент реагирования, который подключен к хранилищу резервов, но также управляет некоторым внутренним состоянием. Внутреннее состояние содержит data
для извлечения и loading
флаг, который используется для визуализации либо текста «Загрузка ...», либо данных после его извлечения. Мне также нужно selectedItems
, что я получаю из магазина редуксов. Эти элементы также используются в других частях приложения, например, на боковой панели, показывающей выбранные в данный момент элементы, поэтому пользователь всегда знает, какие элементы выбраны, и может использовать боковую панель для выбора или удаления элементов.
В этом компонентеselectedItems
используются для сопоставления данных, которые я получаю.
Итак, компонент выглядит примерно так:
export default () => {
const selectedItems = useSelector(state => state.itemsReducer.selectedItems);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchData('someApi/data').then(res => {
setData(res);
setLoading(false);
});
}, [selectedItems]);
return (
<div>
{loading ? (<div>Loading...</div>) : (<div>{data}</div>)}
</div>
);
}
Теперь давайте скажем, что в части <div>{data}</div>
я на самом делесделать некоторое сложное сопоставление, где я использую информацию из selectedItems
для правильного отображения данных.
У меня проблема в следующем: если пользователь добавляет элемент через боковую панель, которая затем меняет selectedItems
всохранить, это означает, что текущий data
больше не действителен, и мне нужно получить новые данные. Поэтому сразу после смены хранилища я бы хотел установить loading
на true
, чтобы отображался текст «Загрузка ...», а затем получать новые данные после монтирования компонента, поэтому в useEffect
. Итак, я знаю, что useEffect
вызывается только после рендеринга компонента. Тем не менее, я сопоставляю data
в JSX, который использует selectedItems
, который уже был обновлен на основе изменения хранилища, но data
все еще старый, так как useEffect
еще не был вызван. Я вообще не хочу отображать data
в течение времени между изменением selectedItems
и получением новых данных. Итак, я в основном хочу, чтобы loading
было установлено в значение true между этими двумя событиями, но как мне это сделать? Задать его в useEffect
слишком поздно, так как компонент уже отрендерен / обновлен, но до этого он уже знал об изменении в selectedItems
(что фактически вызывает ошибку, когда он пытается получить доступ к некоторым свойствам в data
основанный на новом выбранном элементе, но data
все еще старый, поэтому он не содержит его.
Все это похоже на обычный случай, когда приложение реагирует на ситуацию, поэтому я подумал, чтонайти решение быстро, но пока безуспешно.
ОБНОВЛЕНИЕ на основе ответа @ brendangannon
Так что я мог бы привести слишком упрощенный пример. Отображение Я имел в виду отображение данных в JSX. Сначала я отображаю на data
, и у него есть вложенная карта на selectedItems
, он создает структуру в виде таблицы, где строки основаны на data
, и кроме первого столбца,количество столбцов основано на selectedItems
.
Одна вещь, которую я не упомянул, но сейчас кажется уместной, заключается в том, что на самом деле data
попадает в компонент с помощью другого хука.
Итак, вместо useEffect
выше, у меня есть что-то вроде этого:
export default () => {
// ...
const {data, loading} = useLoadData(...someParams);
// ...
}
Хук сохраняет внутреннее состояние и извлекает данные в свой собственный useEffect
, который зависит отselectedItems
и извлекает новые данные в случае изменения selectedItems
на основе этих элементов.
Имеет ли смысл сохранять внутреннее состояние также в моем компоненте, который будет копией data
, давайте назовемэто dataToMap
на данный момент, что будет использоваться в JSX? И я бы также добавил useEffect
с зависимостью [data]
, который бы устанавливал состояние для этого компонента при загрузке новых данных, в основном обновляя dataToMap
. Таким образом, если selectedItems
изменилось, этот компонент будет повторно визуализироваться при использовании устаревшего dataToMap
, а затем внутренний useEffect
из useLoadData
начнет загружать новые данные внутренне, давая мне loading === true
, который в следующемrender будет использовать для отображения текста «Загрузка ...», а затем при получении свежего data
мой хук срабатывает из-за изменения зависимости [data]
, устанавливает новое dataToMap
во внутреннее состояние, что вызывает еще один повторный рендеркоторые могут (и будут, благодаря loading===false
, установленному внутри useLoadData
) теперь безопасно отображать новые данные в состоянии.
Таким образом, компонент будет выглядеть примерно так:
export default () => {
// ...
const selectedItems = useSelector(state => state.itemsReducer.selectedItems);
const {data, loading} = useLoadData(...someParams);
const [dataToMap, setDataToMap] = useState([]);
useEffect(() => {
if (loading === false) {
// new data fetched
setDataToMap(data);
}
}, [data]);
return (
<div>
{loading ? (<div>Loading...</div>) : (
<div>
{dataToMap.map(dataEl => {
return (
<>
// render some row stuff
{selectedItems.map(selItem => {
// render some column stuff
})}
</>
)
})}
</div>
)}
</div>
);
}
Это оставляет меня с двумя вопросами:
- Если я оставлю хук
useLoadData
, это хороший подход? - Есть ли лучший подход для всего этого, учитывая возможность удаления
useLoadData
?
ОБНОВЛЕНИЕ 2
Только что понял, что это приведет к той же самой проблеме - когда устаревший dataToMap
сопоставлен, в то время как selectedItems
уже изменен (новый элемент присутствует), он попытается получить доступ к данным, которыене в dataToMap
.
Я думаю, я просто сохраню selectedItems
внутри себя в состоянии useLoadData
и верну его моему компоненту для отображения, например, selectedItemsForMapping
. Когда selectedBuckets
изменится, он все равно будет использовать старый selectedBucketsForMapping
для рендеринга поверх устаревших данных, и после этого он вернет новое selectedItemsForMapping
вместе с loading===true
, чтобы я мог показать «Загрузка ...» иостальное уже обсуждалось выше.