# Infinite Scroll

> Build an infinite scroll list using the Intersection Observer API — fetch the next page of Rick & Morty characters automatically as the sentinel element enters the viewport.

- Difficulty: intermediate
- Tags: useState, useEffect, fetching, IntersectionObserver
- Estimated time: 45 minutes
- Tests: 12
- Requires subscription: yes

## Overview

Build an infinite scroll list of Rick and Morty characters using the **Intersection Observer API** — automatically fetch the next page as a sentinel element enters the viewport, building on the Suspense-driven `use()` data fetching approach.


## Requirements

- The first page is already fetched with `use()` + Suspense in the starter — keep that as the starting point. Your job is to add the infinite scroll on top of it.
- Follow the API pagination: use `data.info.next` for the next page URL and `info.next` from each response. The fake API returns 4 pages of 6 characters each.
- The next page must load automatically when the user scrolls to the bottom of the list, with no button.
- The sentinel element that triggers the load must have `data-testid="sentinel"`.
- Append the new characters to the existing list — do not replace them.
- Show a loading indicator while fetching the next page (`data-testid="loading-more"`, text `Loading more characters...`).
- Hide the loading indicator once the next page finishes loading.
- Show an end-of-list message (`data-testid="end-of-list"`, text `End of list`) when there are no more pages.
- Stop fetching once the end is reached — no further requests when `info.next` is `null`.
- Disconnect the observer when the component unmounts.


## Notes

- `App` is already complete — title, layout, and the `Suspense` boundary with the `CharactersSkeleton` fallback are wired up.
- Everything happens in `Characters()`: it already renders the first page. Add the state (`nextUrl`, `loading`), the observer, and the sentinel/loading/end-of-list elements there.
- `Components` is read-only.
- Some tests already pass with the starter code (title, skeleton, first page, and even "does not fetch again once the end is reached" — that one passes vacuously because with no observer yet, no fetch is ever triggered, so it doesn't validate anything). The real work is in the tests that require the IntersectionObserver.

The API response shape is:

```typescript
{
  info: {
    count: number;
    pages: number;
    next: string | null;
    prev: string | null;
  }
  results: {
    id: number;
    name: string;
    status: string;
    species: string;
    image: string;
  }
  [];
}
```


## Tests

01. renders the app title
02. shows skeleton while loading
03. renders first-page characters after loading
04. observes the sentinel element
05. loads the next page when the sentinel enters the viewport
06. appends characters instead of replacing them
07. loads the third page on repeated intersections
08. shows a loading indicator while fetching the next page
09. hides the loading indicator once the next page loads
10. shows end of list when there are no more pages
11. does not fetch again once the end is reached
12. disconnects the observer on unmount


