Skip to main content

Refreshing Data

Sometimes data becomes out-of-date.

use-remote-data supports refreshing through an optional refresh: RefreshStrategy parameter to the hook.

A RefreshStrategy is a function that decides whether to keep data or mark it stale.

By time

Here is an example where data is considered stale after 2 seconds.

In the <Await> render prop, you can check the second argument (isStale) to decide whether to let the user see old data or a loading indicator.

import { RefreshStrategy, useRemoteData, Await } from 'use-remote-data';

let i = 0;
const freshData = (): Promise<number> =>
new Promise((resolve) => {
i += 1;
setTimeout(() => resolve(i), 1000);
});

export function Component() {
const store = useRemoteData(freshData, {
refresh: RefreshStrategy.afterMillis(2000),
});

return (
<Await store={store}>
{(num, isStale) => (
<span style={{ color: isStale ? 'darkgray' : 'black' }}>
{num}
</span>
)}
</Await>
);
}

Only sometimes?

You can enable or disable refreshing dynamically by swapping the refresh strategy in or out. For instance:

import { useState } from 'react';
import { RefreshStrategy, useRemoteData, Await } from 'use-remote-data';

let i = 0;
const freshData = (): Promise<number> =>
new Promise((resolve) => {
i += 1;
setTimeout(() => resolve(i), 1000);
});

export function Component() {
const [autoRefresh, setAutoRefresh] = useState(true);
const store = useRemoteData(freshData, {
refresh: autoRefresh ? RefreshStrategy.afterMillis(1000) : undefined,
});

return (
<div>
<label>
Autorefresh:
<input
type="checkbox"
onChange={(e) => setAutoRefresh(!autoRefresh)}
checked={autoRefresh}
/>
</label>
<br />
<Await store={store}>
{(num, isStale) => (
<span style={{ color: isStale ? 'darkgray' : 'black' }}>
{num}
</span>
)}
</Await>
</div>
);
}

Poll until valid data

Some APIs require polling until they return final data. use-remote-data supports this through RefreshStrategy.pollUntil

import { RefreshStrategy, useRemoteData, Await } from 'use-remote-data';

let i = 0;
const freshData = (): Promise<number> =>
new Promise((resolve) => {
i += 1;
setTimeout(() => resolve(i), 1000);
});

export function Component() {
const store = useRemoteData(freshData, {
refresh: RefreshStrategy.pollUntil((x) => x > 2, 1000),
storeName: 'polling-store',
});

return (
<Await store={store}>
{(num, isStale) =>
isStale ? <span>stale data {num}</span> : <span>{num}</span>
}
</Await>
);
}

Refresh on Dependency Change

useRemoteData and useRemoteDataMap let you provide a dependencies array (similar to React's useEffect):

useRemoteData(() => fetchData(), {
dependencies: [props.userId, otherValue],
});

When the dependency array changes, the store refreshes automatically, and triggerUpdate() will re-fetch if needed. Each element is compared using Object.is, the same semantics as React's useEffect dependencies.

Pass primitives, not objects

Since Object.is compares by reference, passing an object or array that gets re-created every render (like { userId: id } or [id, name] built inline) will cause infinite re-fetches. Stick to primitive values (strings, numbers, booleans) in the dependencies array.

import { useState } from 'react';
import { useRemoteData, Await } from 'use-remote-data';

let i = 0;
const freshData = (): Promise<number> =>
new Promise((resolve) => {
i += 1;
setTimeout(() => resolve(i), 1000);
});

export function Component() {
const [dep, setDep] = useState(1);
const store = useRemoteData(freshData, { dependencies: [dep] });

return (
<div>
<button onClick={() => setDep(dep + 1)}>Bump dep</button>
<br />
<Await store={store}>
{(num, isStale) => (
<span style={{ color: isStale ? 'darkgray' : 'black' }}>
{num}
</span>
)}
</Await>
</div>
);
}