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

Something that comes up sometimes is APIs which require you to poll. 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>
);
}