Skip to content

fix: issue #795#797

Open
007qr wants to merge 14 commits into
solidjs-community:mainfrom
007qr:main
Open

fix: issue #795#797
007qr wants to merge 14 commits into
solidjs-community:mainfrom
007qr:main

Conversation

@007qr

@007qr 007qr commented Jun 4, 2025

Copy link
Copy Markdown

This PR fixes the issue: #795

Summary by CodeRabbit

  • New Features

    • Infinite scrolling now exposes loading and error states for fetched pages.
    • Scroll loading is more resilient when the sentinel element is added, removed, or reattached.
  • Bug Fixes

    • Prevents duplicate or stale page appends during fast updates.
    • Improves handling of empty result pages so pagination ends correctly.
    • Cleans up observer behavior to avoid lingering scroll listeners.

@changeset-bot

changeset-bot Bot commented Jun 4, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4e6e6eb

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@solid-primitives/pagination Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/pagination/src/index.ts Outdated

@atk atk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let's discuss the API. I dislike the boolean and that the params need to be wrapped in a function even if they are not supposed to change.

Comment thread packages/pagination/src/index.ts Outdated

/**
* Provides an easy way to implement infinite scrolling.
* A SolidJS utility to create an infinite scrolling experience with IntersectionObserver and reactivity.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to explicitly mention SolidJS here when the whole package is part of solid-primitives.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure will improve this

Comment thread packages/pagination/src/index.ts Outdated
* A function that takes a page number and optional parameters and returns a Promise resolving to an array of items.
*
* @param {Object} [options] - Optional configuration object.
* @param {() => P} [options.params] - A reactive accessor for dynamic fetcher parameters.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually use the pattern that we take a params object or a params object signal. If it is a signal, we expect that we should reset state on params change; otherwise, we don't.

@007qr

007qr commented Oct 21, 2025

Copy link
Copy Markdown
Author

@atk I have updated the api design. Please check once

@007qr 007qr requested review from atk and thetarnav October 31, 2025 12:08
Comment thread packages/pagination/src/index.ts Outdated
export type _E = JSX.Element;

/** Function-shaped accessor with live .loading / .error properties */
export type PagesAccessor<T> = (() => T[]) & {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks awefully similar to a Resource<T>. I don't think we should re-create Solid's types without a good reason.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that was my thought too, Solid’s types felt like a solid (no pun intended) starting point for this hook. Since the JS docs mentioned patterns like

pages.loading  
pages.error

it made sense to align the structure with Solid’s Resource style for familiarity and consistency.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then why can't you use the original type?

@007qr 007qr Apr 22, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed type to use original type now and made some other changes too. Please let me know if you want to discuss the API design any further...

007qr added 2 commits March 18, 2026 09:14
…olidjs-community#795)

  Addresses review feedback on solidjs-community#797: drop the custom PagesAccessor<T> type
  and reuse solid-js's Resource<T> shape inline via Pick, so we're not
  re-creating a type that already exists in solid-js. Attach reactive
  loading/error getters to the pages accessor as documented. Also removes
  the dead _E export and the stacked onCleanup handlers in the loader
  ref-callback.
@007qr 007qr requested a review from atk April 22, 2026 20:07
Comment thread pnpm-workspace.yaml
@007qr

007qr commented Apr 28, 2026

Copy link
Copy Markdown
Author

Hi @thetarnav, Could you please take a look when you get a chance?

@davedbase

Copy link
Copy Markdown
Member

@atk we good with this? If so I'll merge and port the changes to 2.0

@davedbase davedbase added the bug Something isn't working label Jul 4, 2026
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The createInfiniteScroll primitive in the pagination package is rewritten to wrap fetch results with page tags, use res.latest to avoid stale/duplicate appends, track an end state on empty pages, manage a single IntersectionObserver instance with cleanup, expose loading/error on pages, and return refetch in options. A changeset documents the minor release.

Changes

Infinite Scroll Rework

Layer / File(s) Summary
Fetch wrapper and Resource typing
packages/pagination/src/index.ts
Imports Resource type and introduces a wrapped fetch function tagging responses with their requested page, using createSignal for items, page, and end.
Stale-append prevention
packages/pagination/src/index.ts
Appends data only when the response's page matches lastAppended, ignoring stale/duplicate results, and sets end when an empty page arrives.
Observer lifecycle and options wiring
packages/pagination/src/index.ts
Loader manages a single observed element via IntersectionObserver, disconnects on cleanup, exposes loading/error on pages via Object.defineProperties, and returns page, setPage, setPages, end, setEnd, refetch in options.
Changeset documentation
.changeset/pink-carpets-reply.md
Adds a minor version changeset describing the .loading/.error and null-ref handling update.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • solidjs-community/solid-primitives#871: Modifies the same createInfiniteScroll internals and return typing in packages/pagination/src/index.ts, conflicting/overlapping with this PR's changes to pages.loading/error.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related to the PR, but it is too vague to convey the actual change beyond referencing an issue. Rename it to summarize the main change, such as updating createInfiniteScroll to expose loading/error and improve sentinel handling.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/pagination/src/index.ts (1)

384-384: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

refetch leaks the internal Resp<T> wrapper.

The public refetch return type exposes the internal { page, items } shape rather than T[], surfacing an implementation detail to consumers. Consider wrapping it to return T[] (or documenting/narrowing the type) so the public API stays in terms of T.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/pagination/src/index.ts` at line 384, The public refetch API in the
pagination types is exposing the internal Resp<T> wrapper instead of the item
list shape consumers should see. Update the refetch return type in the
pagination module so it returns T[] (or a similarly narrowed public type) rather
than Resp<T>, and make sure the related pagination methods/types stay consistent
with that public contract. Use the refetch signature in the pagination index and
any accompanying pagination result type definitions to locate and adjust the
exposed API.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/pagination/src/index.ts`:
- Around line 425-443: The auto-pagination logic in the IntersectionObserver
callback can stall because it only advances when a new intersection event fires,
and it can also skip a failed page by incrementing page even after an error.
Update the observer handling around the IntersectionObserver setup, setPage,
end(), and res.loading so the sentinel is re-checked whenever loading or error
state changes, and only advance to the next page when the previous fetch
succeeded. If the current request failed, retry the same page instead of
incrementing.

---

Nitpick comments:
In `@packages/pagination/src/index.ts`:
- Line 384: The public refetch API in the pagination types is exposing the
internal Resp<T> wrapper instead of the item list shape consumers should see.
Update the refetch return type in the pagination module so it returns T[] (or a
similarly narrowed public type) rather than Resp<T>, and make sure the related
pagination methods/types stay consistent with that public contract. Use the
refetch signature in the pagination index and any accompanying pagination result
type definitions to locate and adjust the exposed API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 51860ac4-c740-4ef6-ad97-655873207280

📥 Commits

Reviewing files that changed from the base of the PR and between ea6dfd2 and 4e6e6eb.

📒 Files selected for processing (2)
  • .changeset/pink-carpets-reply.md
  • packages/pagination/src/index.ts

Comment on lines +425 to +443
if (!io) {
io = new IntersectionObserver(
entries => {
if (!entries.some(e => e.isIntersecting)) return;
if (end() || res.loading) return;
setPage(p => p + 1);
},
{ root: null, rootMargin: "0px 0px 50px 0px", threshold: 0 },
);
onCleanup(() => {
io?.disconnect();
io = null;
});
}
if (el) {
io.observe(el);
observed = el;
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

Does IntersectionObserver re-invoke its callback when the observed element remains intersecting and no intersection state change occurs?

💡 Result:

No, the IntersectionObserver callback does not re-invoke when an observed element remains intersecting and no intersection state change (crossing a threshold) occurs [1][2][3]. The callback is specifically designed to trigger only when the visibility of the target element crosses one of the observer's defined thresholds [1][2][3]. If the element remains within its current state (i.e., its intersection ratio does not cross any defined threshold), the observer does not fire the callback [1][2][4]. There is one notable exception to this rule: the callback is guaranteed to fire at least once during the first render cycle after the observe method is called, regardless of whether a threshold has been crossed [5]. This initial invocation serves to provide the observer with the target's current intersection status [5]. Following this initial report, subsequent invocations will only occur when a threshold-crossing event takes place [1][2][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file structure first
ast-grep outline packages/pagination/src/index.ts --view expanded

# Show the relevant section with line numbers
sed -n '360,520p' packages/pagination/src/index.ts

# Find where `res.loading`, `end()`, and `setPage` are defined/used
rg -n "res\.loading|end\(\)|setPage\(|IntersectionObserver|onCleanup|error" packages/pagination/src/index.ts

Repository: solidjs-community/solid-primitives

Length of output: 4979


Auto-pagination can stall on intersecting sentinels and skip failed pages.

  • If the sentinel stays intersecting while res.loading flips false, no new observer event fires, so short pages can stop loading until the element leaves and re-enters view.
  • On fetch error, the next intersection advances page instead of retrying the failed request, so the failed page is skipped.

Re-check the sentinel when loading/error state changes, or gate setPage on a successful fetch state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/pagination/src/index.ts` around lines 425 - 443, The auto-pagination
logic in the IntersectionObserver callback can stall because it only advances
when a new intersection event fires, and it can also skip a failed page by
incrementing page even after an error. Update the observer handling around the
IntersectionObserver setup, setPage, end(), and res.loading so the sentinel is
re-checked whenever loading or error state changes, and only advance to the next
page when the previous fetch succeeded. If the current request failed, retry the
same page instead of incrementing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants