Infinite scrolling breaks on page refresh
What is the location of your example repository?
No response
Which package or tool is having this issue?
Hydrogen
What version of that package or tool are you using?
^2024.4.7
What version of Remix are you using?
^2.9.2
Steps to Reproduce
- Follow steps in this guide to implement infinite scrolling
- Paginate to a second page or more causing a deep link URL for that page
- Refresh the page
- Only the second page onwards loads without the first set of pages
Expected Behavior
The first set of pages to load
Actual Behavior
Only the first page of the deep linked page loads
I found the issue, this useMemo here does not have globalThis?.window?.__hydrogenHydrated as a dependency so this is memo-izing and only returning the first page.
I have a hunch that in the examples or other code bases, there are other calls into usePagination that are triggering the useEffect that is setting the global __hydrogenHydrated singleton to true and not running into this issue. This seems to be an issue when there is only one instance of Pagination on the page.
For anyone else running into this issue, I forked the Pagination component and got rid of all instances of __hydrogenHydrated being used and this fixed the issue for me.
This works but it does cause the SSR page to flicker for an instance before the client side hydration kicks in with the previous pages. This also doesn't work if sharing the deep linked URL to a different browser.
I then got both issues by
- Redirecting to the same page without the URL params if there are not previous pages in the navigation state
- Rendering
nullduring SSR if there were pagination params present
const navigate = useNavigate();
const {search, state} = useLocation();
const params = useMemo(() => new URLSearchParams(search), [search]);
useEffect(() => {
if (params.get('direction') && params.get('cursor') && !state?.nodes) {
navigate(window.location.pathname);
}
}, [params, state, navigate]);
if (
typeof document === 'undefined' &&
params.get('direction') &&
params.get('cursor')
) {
return null;
}
The downside to this being that SSR is turned off if the user paginates past the first page.
However, this seemed like the best tradeoff by allowing SSR on first page load but turning it off if the user paginates past the first page and refreshes the page. This also fixes the issue of when a deep linked URL is shared with someone or opened in an incognito window.
With these pros and cons and work arounds, could you advise on how to best to fix this? I'd also be happy to open a PR if I'm provided some guidance, thank you!
@kanehara This is expected behaviour of the pagination component to remain at page 2 when a full page refresh happens on page 2.
This is done in consideration of both SEO and user experience.
SEO - Each page of the pagination should be navigable with real urls. If you look at our hydrogen.shop, each page of the pagination has a cursor hash when allows it to be reachable with real urls. We have buttons to also navigate to previous pages. You can use the infinite loading example to load previous page as well.
User experience - We have gone with the approach to let users to stay on the paginated page they are on, if they accidentally hit full page refresh or simply due to tab going to background and browser decides to make a refresh, they would stay on that page. This is with considerations of bad user experience of user scrolling 10+ pages down the list and a refresh made them start all over again.
A potential path you can take is to store the previous loaded product pages in a localStorage. On hydration, using startTransition (to avoid hydration mismatch), check localStorage for previous store products and join with products loaded from SSR. The goal is that we don't mess with the SSR side with the ability to render on a per page bias but be able to have a full list of products on the client side. For buyers who has previous loaded the previous page, they will get immediate feedback on the products that's already loaded. For buyers who has not load previous pages, they will get delayed feedback on each previous page navigation.