useQuery

This Hook is available since v1.5.0.

A basic React hook for data fetching that supports caching, automatic refreshing, and many other powerful features.

Demo

Source

Immediate + Trigger by user + Dependencies

Data:
Loading...

Lifecycle + Refresh + Params + Mutate + Cancel

Data:
Not loaded
Params:
[]

Throttle + Debounce

Data with debounce:
Not loaded
Data with throttle:
Not loaded

ReFocus + ReConnect + AutoRefresh + Loading Slow

Data:
Initializing...

Error Retry + Cache (SWR)

Data:
Not loaded
Params:
[]
Data2:
Not loaded
Params2:
[]

Usage

Usage

Basic Usage

When the component is mounted, it automatically triggers data fetching and returns data, loading status, and error information, supported by useAsyncFn.

// fetchData is independent of the request library, it can be any async function that returns a Promise, which can be implemented by fetch, axios, graphql, etc. const { loading, data, error } = useQuery(fetchData) // Loading and error UI handling if (loading) return <Loading /> if (error) return <Error /> // Rendering data return <div>{data}</div>

Manual Trigger

When manual is set to true, all built-in automatic behaviors (none of which are enabled by default, including polling loading, focus reloading, network reconnect reloading) are disabled. Also, the default value of immediate is set to false.

immediate is supported by useMount.

const { run, loading, data, error, params } = useQuery(fetchData, { manual: true, initialParams: ['params'], initialData: 'initialData', }) // Manually triggered, which will trigger the update of the params run('newParams')

Refresh Dependencies

Using refreshDependencies, you can set the dependencies for the refresh operation. When any of these dependencies change, a refresh (refresh()) operation will be triggered, supported by useUpdateEffect.

const { loading, data, error } = useQuery(fetchData, { refreshDependencies: [dep1, dep2], })

Cancellation

Using cancel can cancel the current request and reset all states. Note that cancel cannot prevent the Promise from executing, it just terminates the subsequent status update logic, supported by useAsyncFn.

const { cancel, loading, data, error } = useQuery(fetchData) // Cancel the request cancel()

Lifecycle

useQuery provides rich lifecycle support, supported by useAsyncFn, including:

const { loading, data, error } = useQuery(fetchData, { // Before the operation onBefore: (data, params) => console.log('before', data, params), // After the operation onSuccess: (data, params) => console.log('success', data, params), // In case of failure onError: (error, params) => console.log('error', error, params), // Upon completion onFinally: (data, params) => console.log('finally', data, error, params), // When cancelling onCancel: (data, params) => console.log('cancel', data, error, params), // Manual data or parameter modification onMutate: (data, params) => console.log('mutate', data, params), // Refreshing onRefresh: (data, params) => console.log('refresh', data, params), })

Slow Loading State

Using loadingSlow can determine whether the current operation is in a slow loading state, that is, the operation time exceeds the expected threshold. By using this state to render different UIs, user experience can be improved, supported by useLoadingSlowFn.

const { loading, loadingSlow, data, error } = useQuery(fetchData, { loadingTimeout: 3_000, // Default is 0, set to 3 seconds here onLoadingSlow: () => console.log('loading slow'), // Callback for slow loading }) // Display slow loading UI during slow loading if (loading) return <Loading slow={loadingSlow} />

Initialization and Refreshing

Using initializing and refreshing can determine whether it is in the initialization and refreshing state.

These two states are actually derived from loading and data, here is pseudocode:

const initializing = Boolean(!data && loading) const refreshing = Boolean(data && loading)
const { initializing, refreshing, data, error } = useQuery(fetchData) // Display initial loading UI if (initializing) return <Loading />; // Display existing data, while changing UI and other styles based on the refreshing state return <div>{data}{refreshing && <Loading />}</div>

Parameters and Refreshing

The params can be used to retrieve the parameters of the last request, and refresh can be used to re-request with the parameters of the last request, supported by useAsyncFn.

const { run, params, refresh } = useQuery(fetchData) // Retrieve the parameters of the last request, which will be `initialParams` on the first call if it exists, otherwise it will be [] on the first call console.log(params) // Re-request with the last parameters, i.e., 'refresh', equivalent to `run(params)` refresh()

Automatic Clearing and Cancellation

The clearBeforeRun can be used to clear the data before each request, and cancelOnUnmount can be used to automatically cancel the request logic when the component is unmounted (but cannot prevent the Promise from executing), supported by useAsyncFn.

const { loading, data, error } = useQuery(fetchData, { clearBeforeRun: true, // Clear data before each request, default is false cancelOnUnmount: false, // Do not cancel the request when the component is unmounted, default is true })

Debounce and Throttle

Using throttle and debounce options can control the frequency of manual triggering, supported by useThrottledFn and useDebouncedFn.

const { run, loading, data, error } = useQuery(fetchData, { throttle: 1_000, // In cases of frequent triggering, limit triggering to once per second // throttle: { wait: 1_000 }, // Or specify the entire UseThrottledFnOptions object debounce: 1_000, // In cases of frequent triggering, trigger 1 second after the operation stops // debounce: { wait: 1_000 }, // Or specify the entire UseDebouncedFnOptions object })

Data and Parameter Modification

Using mutate can directly modify the data and request parameters, triggering a re-render but not additional requests, supported by useAsyncFn.

const { data, mutate } = useQuery(fetchData) // Update data mutate('newData') // mutate((preData) => 'newData') // Supports setState-style updates // Update both data and parameters mutate('newData', ['newPrams']) // Parameters also support setState-style updates // mutate((preData) => 'newData', (preParams) => ['newPrams']) // Modify the global cache import { mutate } from '@shined/react-use' mutate((key) => key === 'cacheKey', 'newData')

Data Polling

Setting refreshInterval to a number greater than 0 will enable the automatic refresh function, fetching data again at specified intervals, supported by useIntervalFn.

const { loading, data, error } = useQuery(fetchData, { refreshInterval: 5_000, // Polling interval, default 0, off refreshWhenHidden: true, // Whether to poll when the page is not visible, default false refreshWhenOffline: true, // Whether to poll when offline, default false })

Re-Focus and Re-Connect Reloading

Setting refreshOnFocus and refreshOnReconnect to true will fetch data again when the page is focused or the network is reconnected, supported by useReFocusFn and useReConnectFn.

const { loading, data, error } = useQuery(fetchData, { refreshOnFocus: true, refreshOnReconnect: true, refreshOnFocusThrottleWait: 3_000, // Throttle time for refresh on focus, default 5_000, only effective when refreshOnFocus is true })

For usage in non-Web environments like React Native, Ink, you can manually specify the logic for visibility and network status.

import { createReactNativeReFocusRegister } from '@shined/react-use' import { AppState } from 'react-native' const { loading, data, error } = useQuery(fetchData, { isVisible: () => true, // Custom visibility determination function isOnline: () => true, // Custom online status determination function registerReConnect: createReactNativeReFocusRegister(AppState), // Built-in React Native network reconnection registration function registerReFocus: (callback) => {}, // Custom focus event registration function })

Pausable

If manual is not specifically set to false, but you still want to control internal automatic behaviors, you can use the Pausable instance attribute exposed by the Hooks, supported by usePausable.

const { pause, resume, isActive } = useQuery(fetchData, { refreshInterval: 5_000, // Specify to refresh every 5 seconds }) // Pause automatic behaviors pause() // Resume automatic behaviors resume() // Check if it is in an active state console.log(isActive())

Error Retry

Setting errorRetryCount and errorRetryInterval to numbers greater than 0 will automatically retry the request in case of failure, supported by useRetryFn.

const { loading, data, error } = useQuery(fetchData, { errorRetryCount: 3, // Number of retries in case of error, default 0, off errorRetryInterval: 1_000, // Interval between retries, default 0, immediate retry onErrorRetry: (error) => console.log(error), // Callback during error retry onErrorRetryFailed: (error) => console.log(error), // Callback when error retry fails })

Cache and SWR

Setting a cacheKey enables caching functionality. The cached content includes data and param. If cached data exists and has not expired (expired data will be cleared), it will return the cached data first, while still triggering a request to update the cached data, thus ensuring the freshness of the data. This is the SWR (Stale-While-Revalidate) strategy.

const { loading, data, error } = useQuery(fetchData, { cacheKey: 'cacheKey', // Cache key, can be a string or a function returning a string cacheExpirationTime: 5 * 60 * 1000, // Maximum cache time, default 5 minutes, set `false` to disable })

You can specify provider as an external storage (such as Reactive), localStorage, etc., to achieve multi-location shared caching or more detailed local caching. A Provider needs to meet the following interface definition (basically the Map type interface):

export interface UseQueryCacheLike<Data> { get(key: string): Data | undefined set(key: string, value: Data): void delete(key: string): void keys(): IterableIterator<string> }

For instance, some parts can use an independent Map cache for data sharing:

const cache = new Map<string, any>() // Component A const { loading, data, error } = useQuery(fetchData, { cacheKey: 'cacheKeyA', provider: cache, // Using an independent Map as the cache provider }) // Component B const { loading, data, error } = useQuery(fetchData, { cacheKey: 'cacheKeyB', provider: cache, // Using an independent Map as the cache provider }) // Component C, although using the same cacheKey as A, doesn't share the cache because of a different provider const { loading, data, error } = useQuery(fetchData, { cacheKey: 'cacheKeyA', // Not specifying a provider, using the default global shared Map })

Or using localStorage as the cache provider to achieve data persistence after page refreshes.

const localStorageProvider = { get: (key: string) => { const value = localStorage.getItem(key) return value ? JSON.parse(value) : undefined }, set: (key: string, value: string) => localStorage.setItem(key, JSON.stringify(value)), delete: (key: string) => localStorage.removeItem(key), keys: () => Object.keys(localStorage)[Symbol.iterator](), } const { loading, data, error } = useQuery(fetchData, { cacheKey: 'cacheKey', provider: localStorageProvider, // Using localStorage as the cache provider })

If both cacheKey and provider are the same, they will be considered as the same cache. Any state, data, or request change in one part of the cache will be synchronized to other parts.

This situation is common when multiple components use the same data set, such as user information, which may be used in nav components, header components, sidebar components, etc. In this case, cacheKey and provider can be used to share data.

// Component A const { loading, data, mutate, refresh } = useQuery(fetchData, { cacheKey: 'cacheKey', }) // Component B const { data, params } = useQuery(fetchData, { cacheKey: 'cacheKey', }) // Execute refresh or mutate operation in Component A refresh() mutate('newData', ['newParams']) // In Component B, data and params will be updated synchronously console.log(data, params) // 'newData', ['newParams']

Custom Data Update

By using compare, you can customize the logic for data updates to prevent frequent refreshes. This is particularly useful in dealing with some pseudo-differences in data, such as timestamps, and it can reduce unnecessary rendering, shallowEqual is used by default, supported by useAsyncFn.

const { loading, data, error } = useQuery(fetchData, { compare: (preData, nextData) => { // Compare whether the data is the same, return true to not update, return false to update return preData === nextData // Or compare a certain field of data, for example, the timestamp might be different, but the actual effective data is the same return deepCompare(preData?.data, nextData?.data) // But please note, when the data size is too large, deep comparison will affect performance, so use it judiciously }, })

Dependencies Collection

useQuery implements a dependency collection strategy, achieving on-demand rendering and maximizing performance optimization, supported internally by useTrackedRefState.

// All state changes will trigger re-rendering, not recommended!!! const fn = useQuery(fetchData) // Changes in loading, data, or error states will trigger re-rendering const { loading, data, error } = useQuery(fetchData) // Only changes in the loading state will trigger re-rendering; changes in data and error states will not. const { loading } = useQuery(fetchData) // All supported state attributes for dependency collection const { loading, data, error, params, loadingSlow, initializing, refreshing } = useQuery(fetchData)

For more details, see Dependencies Collection.

Source

Click links below to view source on GitHub.

API

const { run, data, loading, refreshing, initializing, error, cancel, refresh, mutate, loadingSlow, ...pausable } = useQuery(fetcher, options)

Fetcher

An asynchronous function for data fetching that returns a Promise, independent of request libraries. The following are valid Fetcher functions:

// Native Fetch request const fetchData = async () => await (await fetch('https://api.example.com/data').json()) // Axios request const fetchData = () => axios.get('https://api.example.com/data').then(res => res.data) // GraphQL request const fetchData = () => graphqlClient.query({ query: gql`{ data }` }).then(res => res.data) // Custom Promise function const fetchData = () => new Promise(resolve => setTimeout(() => resolve('data'), 1000)) // Can throw errors const fetchData = () => new Promise((_, reject) => setTimeout(() => reject('error'), 1000))

Options

For more details, see UseLoadingSlowFnOptions, UseReConnectOptions, UseReFocusOptions, UseThrottledFnOptions, UseDebouncedFnOptions, UseIntervalFnInterval and UseRetryFnOptions.

export interface UseQueryOptions<T extends AnyFunc, D = Awaited<ReturnType<T>>, E = any> extends Omit<UseLoadingSlowFnOptions<T, D, E>, 'initialValue'>, Pick<UseReConnectOptions, 'registerReConnect'>, Pick<UseReFocusOptions, 'registerReFocus'> { /** * Disable all automatic refresh behaviors, default is off * * @defaultValue false */ manual?: boolean /** * Initial data passed to fetcher when first mount * * @defaultValue undefined */ initialData?: D | undefined /** * Cache key, can be a string or a function that returns a string * * @defaultValue undefined */ cacheKey?: string | ((...args: Parameters<T> | []) => string) /** * Max cache time, will clear the cache after the specified time * * default is 5 minutes, set `false` to disable */ cacheExpirationTime?: number | false /** * Cache provider, it can be set to an external store (reactive), localStorage, etc. * * Needs to comply with the CacheLike interface definition, defaults to a globally shared `new Map()` * * @defaultValue global shared `new Map()` */ provider?: Gettable<CacheLike<D>> /** * ThrottleOptions => only affects the frequency of manually executing the run method * * @defaultValue undefined */ throttle?: UseThrottledFnOptions['wait'] | UseThrottledFnOptions /** * DebounceOptions => only affects the frequency of manually executing the run method * * @defaultValue undefined */ debounce?: UseDebouncedFnOptions['wait'] | UseDebouncedFnOptions /** * Whether to reload when focus is obtained, default is off * * @defaultValue false */ refreshOnFocus?: boolean /** * Throttle time when obtaining focus, default 5_000 (ms), only valid when `refreshOnFocus` is true * * @defaultValue 5_000 */ refreshOnFocusThrottleWait?: number /** * Custom visibility judgment function * * @defaultValue defaultIsVisible */ isVisible?: () => Promisable<boolean> /** * Whether to reload when network reconnects, default is off * * @defaultValue false */ refreshOnReconnect?: boolean /** * Custom online judgment function * * @defaultValue defaultIsOnline */ isOnline?: () => Promisable<boolean> /** * Interval time for automatic refresh, default is 0, off * * @defaultValue 0 */ refreshInterval?: Exclude<UseIntervalFnInterval, 'requestAnimationFrame'> /** * Whether to reload when hidden, default is off * * @defaultValue false */ refreshWhenHidden?: boolean /** * Whether to reload when offline, default is off * * @defaultValue false */ refreshWhenOffline?: boolean /** * The dependencies of the refresh operation, when the dependencies change, the refresh operation will be triggered * * @defaultValue [] */ refreshDependencies?: DependencyList /** * Error retry count * * @defaultValue 0 */ errorRetryCount?: UseRetryFnOptions<E>['count'] /** * Error retry interval * * @defaultValue 0 */ errorRetryInterval?: UseRetryFnOptions<E>['interval'] /** * Whether to clear the cache before each request * * @defaultValue false */ onErrorRetry?: UseRetryFnOptions<E>['onErrorRetry'] }

Returns

Retuerns contain Pausable instance that can be paused, resumed.

See Pausable for more details.

See UseLoadingSlowFnReturns for more details.

export interface UseQueryReturns<T extends AnyFunc, D = Awaited<ReturnType<T>>, E = any> extends Pausable, Omit<UseLoadingSlowFnReturns<T, D, E>, 'value'> { /** * The data returned by the request */ data: D | undefined /** * Whether the request is in the initialization state (no data + loading, initializing => !data && loading) */ initializing: boolean /** * Whether the request is refreshing data (has data + loading, refreshing => data && loading) */ refreshing: boolean }