useUrlPreview
The hook. Fetch link metadata, render whatever UI you want.
useUrlPreview is the lower-level primitive. The <LinkPreview /> component
is implemented on top of it. Reach for the hook when you want a custom layout,
need to show metadata in places a card doesn't fit (chips, mentions, list
rows), or want to react to the data outside of rendering.
Signature
function useUrlPreview(
url: string,
timeout?: number // default 3000ms, clamped to [1000, 30000]
): {
loading: boolean;
data: LinkPreviewResponse | null;
error: string | null;
};The hook re-runs whenever url or timeout changes. Concurrent renders for
the same URL share a single in-flight request — only one network call goes
out, even across many components.
Recipes
Inline mention chip
import { Image, Pressable, Text, View, Linking } from 'react-native';
import { useUrlPreview } from 'react-native-preview-url';
export function Mention({ url }: { url: string }) {
const { loading, data, error } = useUrlPreview(url);
if (loading) return <Text>…</Text>;
if (error || !data) return <Text>{url}</Text>;
return (
<Pressable onPress={() => Linking.openURL(data.url)}>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
{data.favicons?.[0] && (
<Image
source={{ uri: data.favicons[0] }}
style={{ width: 14, height: 14 }}
/>
)}
<Text style={{ fontWeight: '600' }}>{data.siteName ?? data.title}</Text>
</View>
</Pressable>
);
}Conditional rendering
const { data } = useUrlPreview(url);
return (
<Card>
<Title>{post.body}</Title>
{data?.images?.[0] && <FeatureImage src={data.images[0].url} />}
</Card>
);Reacting outside of render
Because the hook is just { loading, data, error }, you can useEffect on
the data:
const { data } = useUrlPreview(url);
useEffect(() => {
if (data?.siteName) {
setHeaderTitle(data.siteName);
}
}, [data]);Error states
error is a human-readable string. The most common values:
| Error | When |
|---|---|
URL is required | url is empty / falsy. |
Invalid URL format | url is set but not a valid http(s) URL. |
Request timed out | The request didn't complete within timeout (+ a small buffer). |
Error <status> | Server returned a non-2xx without a JSON body. |
| (server-provided) | Server returned a JSON { "error": "..." } body — that string is used. |
Timeout errors are not cached (they're treated as transient). Other server errors are cached for 30 seconds by default to avoid hammering a flaky endpoint. See Caching to tune those windows.
URL is required and Invalid URL format short-circuit synchronously — no
network call is made, no cache entry is created.
Loading semantics
loading is true only when an actual network request is in flight. If the
URL has a fresh cache entry (success or error), the hook returns
synchronously on first render with loading: false. This means you can render
without a loading flash for warm cache hits.
const { loading, data } = useUrlPreview(url);
// warm cache: { loading: false, data: <full object> }
// cold: { loading: true, data: null }Cancellation
If the component unmounts (or url changes) mid-fetch, the hook cancels its
state updates — you won't get a "set state on unmounted component" warning.
The underlying fetch is not aborted on unmount because another mounted
consumer of the same URL likely wants the result. The completed response is
still written to the cache, so the next render of any consumer benefits.