You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit a114253
Browse filesBrowse the repository at this point in the historyBrowse files
You don't need the effect. An empty list puts the sentinel inside the
viewport, so the observer already asks for the first page. The effect was a
second copy of the fetch, the error handling, and the loading flag, racing the
observer for the same request. The `loading: true` initial state only existed
to referee that race, so it goes too.
Four states become three. `loading` and `error` booleans could both be set at
once, which is not a state this component has; one `status` union removes it
and lets the button derive its own label. The `useCallback` goes as well: the
hook reads `onChange` from a ref, so a plain function is enough.
The two buttons merge into one that says "Try again" after a failure.
Verified against the browser's own IntersectionObserver, not the mock, since
the mock does not replay state to a newly created observer and hides the
behavior this recipe depends on. Six cases: first page loads with no effect,
a short page keeps filling, each page is requested exactly once, observation
stops on the last page, the button recovers from an error, and a failure does
not turn into a scroll-retry loop.
The re-arm is worth knowing about. Flipping `skip` drops and recreates the
observer, which is what lets a page too short to push the sentinel out of view
keep loading. The prose now says so instead of describing `skip` as only a
duplicate-request guard.
@@ -224,6 +204,12 @@ export function InfiniteList<T>({
224
204
225
205
<ObserverDemorecipe="infinite-list" />
226
206
227
-
Start with `loading: true` so the sentinel cannot race the first request. After that, the `skip` guard blocks duplicate requests. `rootMargin` starts the load before the sentinel reaches the viewport, so tune the `400px` value to your network and item size. This example sends `items.length` as the next page's `skip`; use whatever pagination your API expects.
207
+
There is no effect here, and that is the point. An empty list puts the sentinel inside the viewport, so the observer asks for the first page through the same path as every page after it. An effect for the first page would duplicate the fetch, the error handling, and the loading flag, then race the observer for the same request.
208
+
209
+
One `status` beats separate `loading` and `error` booleans: the component cannot be loading and failed at the same time, and the retry button reads its own label from it.
210
+
211
+
`skip` does two jobs. It keeps a second request from starting while one is in flight, and because the hook drops and recreates the observer when `skip` flips back, it re-arms the sentinel. A page too short to push the sentinel out of view keeps loading until the viewport fills.
212
+
213
+
`rootMargin` starts the request before the sentinel reaches the viewport. Tune the `400px` value to your network and item size. This example sends `items.length` as the next page's `skip`; use whatever pagination your API expects.
228
214
229
-
Keep the manual "Load more" button and the retry path. Scrolling should never be the only way to fetch content.
215
+
Keep the button. It retries after an error, it is the only route for keyboard and assistive-technology users, and it is the guaranteed way forward if the observer never fires again. Scrolling should never be the only way to fetch content.
0 commit comments