У меня что-то вроде:
const [loading, setLoading] = useState(false);
...
setLoading(true);
doSomething(); // <--- when here, loading is still false.
Состояние настройки по-прежнему асинхронное, так как лучше всего дождаться завершения этого вызова setLoading()?
setLoading(), похоже, не принимает обратный вызов, как раньше.
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));
};
В приведенном выше примере мы хотим запускать каждый вызов setWhatever последовательно. Означает ли это, что нам нужно настроить множество различных хуков useEffect, чтобы воспроизвести это поведение?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Дождитесь повторного рендеринга вашего компонента.
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>
</>
);
}
@Colin вы можете думать об useEffect как о componentDidMount или componentDidUpdate в зависимости от последнего аргумента, переданного обратному вызову в примере выше [загрузка]
Честно говоря, я не думаю, что это хороший ответ; Можете ли вы лучше объяснить, что на самом деле делает doSomething и каков ваш вариант использования?
Я думаю, что на данный момент это имеет смысл. Это надуманный пример, но doSomething() извлекается из API и должен знать текущее состояние действительный, если это имеет смысл.
@Colin Хорошее объяснение с примерами хуков API egghead.io/lessons/react-use-the-usestate-react-hook
Тогда да, useEffect - это то, что вам нужно
Почему бы не вызвать setLoading в эффекте, если doSomething правильно выбирает из Api, может быть так: useEffect(() => { setLoading(true); doSomething().finally(() => setLoading(false))}, []). Обратите внимание, что [] используется для выполнения эффектов только таких, как componentDidMount.
Сеттер 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, программа обновления для перехвата 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);
};
Итак, в случае наличия нескольких setState, которых мы хотим дождаться, нам нужно несколько хуков useEffect, верно?
@Colin, если вы хотите выполнять разные действия при изменении разных состояний, тогда да, но если вы хотите сделать то же самое при любом из этих изменений нескольких состояний, вы можете передать несколько аргументов, таких как [isLoading, isUpdated, showError]
да, так что в приведенном выше случае у вас будет [isLoading, orders]
но чтобы выполнять разные действия, нам нужно несколько хуков useEffect, верно? Кроме того, как мы можем таким образом выполнять последовательные действия? Разве изменение не приведет к одновременному срабатыванию обоих хуков?
что вы подразумеваете под последовательными действиями. Если вы обновите свой код до желаемого поведения, я могу взглянуть и предложить решение, если я его знаю.
Обновил ответ
Этот крючок для usePrevious - приятный штрих, я такого раньше не видел - спасибо, что поделились!
Кстати, вы можете использовать несколько объявлений useEffect, каждое из которых связано с интересующим вас свойством.
У меня есть предложение по этому поводу.
Вы могли бы использовать React Ref для хранения состояния переменной состояния. Затем обновите переменную состояния с помощью response ref. Это отобразит обновление страницы, а затем будет использовать React Ref в асинхронной функции.
const stateRef = React.useRef().current
const [state,setState] = useState(stateRef);
async function some() {
stateRef = { some: 'value' }
setState(stateRef) // Triggers re-render
await some2();
}
async function some2() {
await someHTTPFunctionCall(stateRef.some)
stateRef = null;
setState(stateRef) // Triggers re-render
}
Создан настраиваемый перехватчик useState, который работает аналогично обычному перехватчику useState, за исключением того, что функция обновления состояния для этого настраиваемого перехватчика принимает обратный вызов, который будет выполнен после обновления состояния и повторной визуализации компонента.
Машинописное решение
import { useEffect, useRef, useState } from 'react';
type OnUpdateCallback<T> = (s: T) => void;
type SetStateUpdaterCallback<T> = (s: T) => T;
type SetStateAction<T> = (newState: T | SetStateUpdaterCallback<T>, callback?: OnUpdateCallback<T>) => void;
export function useCustomState<T>(init: T): [T, SetStateAction<T>];
export function useCustomState<T = undefined>(init?: T): [T | undefined, SetStateAction<T | undefined>];
export function useCustomState<T>(init: T): [T, SetStateAction<T>] {
const [state, setState] = useState<T>(init);
const cbRef = useRef<OnUpdateCallback<T>>();
const setCustomState: SetStateAction<T> = (newState, callback?): void => {
cbRef.current = callback;
setState(newState);
};
useEffect(() => {
if (cbRef.current) {
cbRef.current(state);
}
cbRef.current = undefined;
}, [state]);
return [state, setCustomState];
}
Решение Javascript
import { useEffect, useRef, useState } from 'react';
export function useCustomState(init) {
const [state, setState] = useState(init);
const cbRef = useRef();
const setCustomState = (newState, callback) => {
cbRef.current = callback;
setState(newState);
};
useEffect(() => {
if (cbRef.current) {
cbRef.current(state);
}
cbRef.current = undefined;
}, [state]);
return [state, setCustomState];
}
использование
const [state, setState] = useCustomState(myInitialValue);
...
setState(myNewValueOrStateUpdaterCallback, () => {
// Function called after state update and component rerender
})
Хм, так мне нужно добавить
useEffect()для отслеживания каждого состояния и реагирования на него?