Пользовательские хуки могут выполняться несколько раз. Вы должны спроектировать это так, чтобы все, что вы хотите сделать только один раз (например, вызов API), находилось внутри хука useEffect
. Это может быть достигнуто с помощью обратного вызова, который затем вызывается в хуке.
Кроме того, немного более безопасны для типов:
const usePromise = <T>(task: () => Promise<T>) => {
const [state, setState] = useState<[T?, boolean, Error?]>([null, true, null]);
useEffect(() => {
task()
.then(result => setState([result, false, null])
.catch(error => setState([null, false, error]);
}, []); // << omit the condition here, functions don't equal each other²
return state;
};
// Then used as
usePromise(() => apiCall(5));
² да, обычно это плохая практика, но, как мне кажется, task
здесь не должно менятьсяэто нормально
По запросу вот версия, которую я использую в некоторых своих проектах:
export function useAPI<Q, R>(api: (query: Q) => Promise<R | never>) {
const [state, setState] = useState<{ loading?: true, pending?: true, error?: string, errorCode?: number, result?: R }>({ pending: true });
async function run(query: Q) {
if(state.loading) return;
setState({ loading: true });
try {
const result = await api(query);
setState({ result });
} catch(error) {
if(error instanceof HTTPError) {
console.error(`API Error: ${error.path}`, error);
setState({ error: error.message, errorCode: error.code });
} else {
setState({ error: error.message, errorCode: NaN });
}
}
}
function reset() {
setState({ pending: true });
}
return [state, run, reset] as const;
}