Guides / Tutorials

Dog breed images in React Native

By the Dog Image API team. Updated .

Fetch GET /api/v1/breeds, render the names in a FlatList, and when the user taps one, fetch GET /api/v1/breeds/{slug} and give the preview_512 URL of a view to the Image component. No key is needed, and the credit link goes under the picture.

The images are AI-generated illustrations of one adult dog from nine fixed views, on a transparent background. They are not photos, and you should not present them as photos of a real animal.

The key stays on your server

React Native bundles your JavaScript into the app, so a key placed in it is public. This screen uses three keyless things: the catalog, the breed detail and the preview files. Full-size PNG files, at 2048 pixels, need a key. Let your backend download them once, store them, and serve them to the app. If you want that setup, read Cache and self-host dog breed images.

About WebP

The previews are WebP. The React Native Image documentation carries notes about WebP on iOS and on custom Android builds. For a current project, WebP previews are expected to render on iOS 14 and later and on Android, because both platforms decode WebP themselves. Test one preview on a real iOS and a real Android device before you build the screen around it. If a device shows nothing, check the onError callback first.

Types and fetch helpers

const API = 'https://dogimage.dev/api/v1';

type Breed = { slug: string; name: string; default_variant: string };

type ViewImages = { preview_512: string; preview_1024: string };

type BreedDetail = {
  slug: string;
  name: string;
  default_variant: string;
  variants: { slug: string; name: string; views: Record<string, ViewImages> }[];
};

async function getJson<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Request failed with ${response.status}`);
  }
  return response.json() as Promise<T>;
}

export const fetchBreeds = () =>
  getJson<{ breeds: Breed[] }>(`${API}/breeds`).then((data) => data.breeds);

export const fetchBreed = (slug: string) => getJson<BreedDetail>(`${API}/breeds/${slug}`);

Errors come back as JSON with an error.code. For this screen a plain status check is enough.

The picker and the image

The list and the picture live in one screen. The FlatList shows every breed from the catalog, so you add no names by hand.

import { useEffect, useState } from 'react';
import {
  ActivityIndicator, FlatList, Image, Linking, Pressable, StyleSheet, Text, View,
} from 'react-native';

export default function BreedScreen() {
  const [breeds, setBreeds] = useState<Breed[]>([]);
  const [selected, setSelected] = useState<BreedDetail | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetchBreeds().then(setBreeds).catch(() => setError('Could not load breeds.'));
  }, []);

  async function choose(slug: string) {
    setLoading(true);
    setError(null);
    try {
      setSelected(await fetchBreed(slug));
    } catch {
      setError('Could not load that breed.');
    } finally {
      setLoading(false);
    }
  }

  const variant = selected
    ? selected.variants.find((v) => v.slug === selected.default_variant) ?? selected.variants[0]
    : null;
  const side = variant?.views['right'];

  return (
    <View style={styles.screen}>
      <View style={styles.preview}>
        {loading && <ActivityIndicator />}
        {!loading && side && (
          <Image
            source={{ uri: side.preview_512 }}
            style={styles.image}
            resizeMode="contain"
            accessibilityLabel={`${selected?.name}, side view`}
          />
        )}
      </View>

      {selected && (
        <Pressable onPress={() => Linking.openURL(`https://dogimage.dev/dogs/${selected.slug}`)}>
          <Text style={styles.credit}>{selected.name} image: Dog Image API</Text>
        </Pressable>
      )}
      {error && <Text style={styles.error}>{error}</Text>}

      <FlatList
        data={breeds}
        keyExtractor={(item) => item.slug}
        renderItem={({ item }) => (
          <Pressable style={styles.row} onPress={() => choose(item.slug)}>
            <Text>{item.name}</Text>
          </Pressable>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  screen: { flex: 1, padding: 16 },
  preview: { height: 260, alignItems: 'center', justifyContent: 'center', backgroundColor: '#eee' },
  image: { width: 256, height: 256 },
  credit: { color: '#0a58ca', marginVertical: 8 },
  error: { color: '#b00020' },
  row: { paddingVertical: 12, borderBottomWidth: StyleSheet.hairlineWidth },
});

Three points about this code:

  • The Image has a fixed width and height. Network images need both.
  • The gray box behind the image is deliberate. The files are transparent, so the box shows where the canvas is.
  • The credit text is the breed name, then "image: Dog Image API", linked to that breed's page. That is the same form the Pug page shows.

A thumbnail in each row

A row can show a small image too, but that is one request per breed. The catalog does not include preview URLs, so the detail call is needed for each. Load a thumbnail only for the rows the user is looking at, or for a short list of favorites. For a long list, a text row is the better choice.

Choosing a view

right and left are side views, front faces the camera and head is a portrait. Pick one view for the whole list so the pictures read as one set. The front view gallery shows every breed at once if you want to compare before choosing.

When you need bigger files

A 1024 pixel preview covers most phone screens. For a poster, a printed card or a 2048 pixel hero, use the full-size PNG from your own backend. The license explains where each file type can be used.

Questions

Can I put the API key in a React Native app with an environment variable?

No. A value bundled into the app can be read by anyone who has the app. Previews need no key, and full-size files should be fetched by your backend.

Do I need width and height on the Image?

Yes. React Native cannot size a network image before it loads, so set a width and height, or a style that gives it a size.

Which preview URL should I use?

Use preview_512 for list rows and avatars and preview_1024 for a large image. Both are transparent WebP files.

Does the API have a random dog endpoint?

No. You choose a breed slug, a variant and a view, or read them from the catalog.

Next steps