Loading States in BookNest

A screen that fetches data has three states, loading, failed and ready, plus refreshing on top of ready. Model them as one discriminated union instead of separate isLoading and error booleans, and impossible combinations cannot be written. BookNest's catalog reads from api.ts, whose fetchBooks() stands in for fetch('https://api.example.com/books'): it waits 1.5 seconds, returns the six sample books, and fails its very first call so that the error state appears on every cold start.

Catalog.tsx (excerpt): one state machine for loading, error, ready and refreshingCSS
type Load = { status: 'loading' } | { status: 'error'; message: string }
  | { status: 'ready'; books: Book[] };
export default function Catalog() {
  const { colors } = useTheme();
  const [load, setLoad] = useState<Load>({ status: 'loading' });
  const [refreshing, setRefreshing] = useState(false);
  const fetchInto = useCallback(async (isRefresh: boolean) => {
    if (isRefresh) setRefreshing(true);
    else setLoad({ status: 'loading' });
    try {
      setLoad({ status: 'ready', books: await fetchBooks() });
    } catch (e) { // a failed refresh keeps the books already on screen
      setLoad((prev) => prev.status === 'ready' ? prev
        : { status: 'error', message: String(e) });
    } finally {
      setRefreshing(false);
    }
  }, []);
  useEffect(() => { fetchInto(false); }, [fetchInto]);

The JSX switches on load.status: a centered large ActivityIndicator for loading, the message and a Try again button calling fetchInto(false) for error, and for ready a FlatList whose RefreshControl calls fetchInto(true). A refresh keeps the old list under the pull-down spinner, and a failed refresh leaves the books in place instead of replacing them with an error page.

BookNest's catalog while loading, after its first request fails, and mid-pull on the refresh control after Try again, with the emulator in dark mode
BookNest's catalog while loading, after its first request fails, and mid-pull on the refresh control after Try again, with the emulator in dark mode

In a larger app, TanStack Query 79,069 (TanStack Query Fundamentals) provides these states for you as isPending, isError, isRefetching and refetch, with caching and retries on top.