Выполнение асинхронного кода при обновлении состояния с помощью ответных хуков
У меня есть что-то вроде:
const [loading, setLoading] = useState(false);
...
setLoading(true);
doSomething(); // <--- when here, loading is still false.
Установка состояния по-прежнему асинхронна, так как лучше всего дождаться setLoading()
вызова setLoading()
?
setLoading()
, кажется, не принимает обратный вызов, как setState()
раньше.
пример
класс на основе
getNextPage = () => {
// This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
goToTop();
if (this.state.pagesSeen.includes(this.state.page + 1)) {
return this.setState({
page: this.state.page + 1,
});
}
if (this.state.prefetchedOrders) {
const allOrders = this.state.orders.concat(this.state.prefetchedOrders);
return this.setState({
orders: allOrders,
page: this.state.page + 1,
pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
prefetchedOrders: null,
});
}
this.setState(
{
isLoading: true,
},
() => {
getOrders({
page: this.state.page + 1,
query: this.state.query,
held: this.state.holdMode,
statuses: filterMap[this.state.filterBy],
})
.then((o) => {
const { orders } = o.data;
const allOrders = this.state.orders.concat(orders);
this.setState({
orders: allOrders,
isLoading: false,
page: this.state.page + 1,
pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
// Just in case we're in the middle of a prefetch.
prefetchedOrders: null,
});
})
.catch(e => console.error(e.message));
},
);
};
преобразовать в функцию на основе
const getNextPage = () => {
// This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
goToTop();
if (pagesSeen.includes(page + 1)) {
return setPage(page + 1);
}
if (prefetchedOrders) {
const allOrders = orders.concat(prefetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
return;
}
setIsLoading(true);
getOrders({
page: page + 1,
query: localQuery,
held: localHoldMode,
statuses: filterMap[filterBy],
})
.then((o) => {
const { orders: fetchedOrders } = o.data;
const allOrders = orders.concat(fetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
setIsLoading(false);
})
.catch(e => console.error(e.message));
};
Выше мы хотим запускать каждый вызов setWh независимо последовательно. Означает ли это, что нам нужно установить множество различных хуков useEffect для репликации этого поведения?
Ответы
Ответ 1
useState
не обеспечивает обратный вызов после обновления состояния, как setState в компонентах класса React. Чтобы повторить то же поведение, вы можете использовать похожий шаблон, такой как метод жизненного цикла componentDidUpdate
в компонентах класса React с помощью useEffect
с использованием хуков.
useEffect
принимают второй параметр в виде массива значений, которые React должен отслеживать на предмет изменений после завершения цикла рендеринга.
const [loading, setLoading] = useState(false);
...
useEffect(() => {
doSomething(); // This is be executed when 'loading' state changes
}, [loading])
setLoading(true);
РЕДАКТИРОВАТЬ
В отличие от setState
, setState
обновления для useState
не имеет обратного вызова, но вы всегда можете использовать useEffect
для воспроизведения описанного выше поведения. Однако вам нужно определить изменение загрузки
Функциональный подход к вашему коду будет выглядеть
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const prevLoading = usePrevious(isLoading);
useEffect(() => {
if (!prevLoading && isLoading) {
getOrders({
page: page + 1,
query: localQuery,
held: localHoldMode,
statuses: filterMap[filterBy],
})
.then((o) => {
const { orders: fetchedOrders } = o.data;
const allOrders = orders.concat(fetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
setIsLoading(false);
})
.catch(e => console.error(e.message));
}
}, [isLoading, preFetchedOrders, orders, page, pagesSeen]);
const getNextPage = () => {
// This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
goToTop();
if (pagesSeen.includes(page + 1)) {
return setPage(page + 1);
}
if (prefetchedOrders) {
const allOrders = orders.concat(prefetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
return;
}
setIsLoading(true);
};
Ответ 2
Подождите, пока ваш компонент не выполнит рендеринг.
const [loading, setLoading] = useState(false);
useEffect(() => {
if (loading) {
doSomething();
}
}, [loading]);
setLoading(true);
Вы можете улучшить ясность с чем-то вроде:
function doSomething() {
// your side effects
// return () => { }
}
function useEffectIf(condition, fn) {
useEffect(() => condition && fn(), [condition])
}
function App() {
const [loading, setLoading] = useState(false);
useEffectIf(loading, doSomething)
return (
<>
<div>{loading}</div>
<button onClick={() => setLoading(true)}>Click Me</button>
</>
);
}
Ответ 3
Вам нужно вызвать ' setState
' (setLoading) немного по-другому...
function App() {
const [loading, setLoading] = useState(false);
return (
<div>
{JSON.stringify(loading)} // to see our state
<button onClick={() => setLoading(true)}>Click Me</button>
</div>
);
}
Демо: https://codesandbox.io/s/xvmp92zqjz