Implement a useQuery hook that manages a promise resolution which can be used to fetch data.
export default function Component({ param }) {const request = useQuery(async () => {const response = await getDataFromServer(param);return response.data;}, [param]);return (<div>{request.loading && <p>Loading...</p>}{request.error && <p>Error: {request.error.message}</p>}{request.data && <p>Data: {request.data}</p>}</div>);}
fn: () => Promise: A function that returns a promisedeps: DependencyList: An array of dependencies, similar to the second argument of useEffect. Unlike useEffect, this defaults to []The hook returns an object that has different properties depending on the state of the promise.
status: 'loading': The promise is still pendingstatus: 'error': The promise was rejectederror: Error: The error that caused the promise to be rejectedstatus: 'success': The promise was resolveddata: The data resolved by the promise returned by fnThe useQuery hook can be implemented with useEffect to begin the promise resolution and update the states accordingly.
The challenge here is to realize that promise resolutions are asynchronous from React updates, so there is a possibility of race conditions when the dependencies change before a pending promise is resolved.
To prevent this, we can use an ignore flag to ignore the promise resolution if it is no longer relevant (e.g. deps have changed, the component has been unmounted). The ignore is initialized within the function's closure; each time useEffect runs, the function has its own ignore instance variable and can refer to it when the promise is resolved and it has to decide whether to use the results.
This approach is well-documented in the React documentation.
import { DependencyList, useEffect, useState } from 'react';type AsyncState<T> =| { status: 'loading' }| { status: 'success'; data: T }| { status: 'error'; error: Error };export default function useQuery<T>(fn: () => Promise<T>,deps: DependencyList = [],): AsyncState<T> {const [state, setState] = useState<AsyncState<T>>({status: 'loading',});useEffect(() => {let ignore = false;setState({ status: 'loading' });fn().then((data) => {if (ignore) {return;}setState({ status: 'success', data });}).catch((error) => {if (ignore) {return;}setState({ status: 'error', error });});return () => {ignore = true;};}, deps);return state;}
console.log() statements will appear here.