React Native Preview URL

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:

ErrorWhen
URL is requiredurl is empty / falsy.
Invalid URL formaturl is set but not a valid http(s) URL.
Request timed outThe 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.

On this page