Skip to content

upgrade: upload package upgrade for Solid 2.0#846

Open
davedbase wants to merge 8 commits into
solidjs-community:nextfrom
davedbase:update/v2/upload
Open

upgrade: upload package upgrade for Solid 2.0#846
davedbase wants to merge 8 commits into
solidjs-community:nextfrom
davedbase:update/v2/upload

Conversation

@davedbase
Copy link
Copy Markdown
Member

@davedbase davedbase commented Apr 21, 2026

Summary

  • Complete rewrite of @solid-primitives/upload targeting Solid.js 2.0 (solid-js@^2.0.0-beta.10, @solidjs/web@^2.0.0-beta.10)
  • Renamed createFileUploadcreateFileUploader for naming consistency with the package
  • Replaced the @solid-primitives/utils dependency with inline equivalents (the utils package is not yet on Solid 2.0)
  • Stage reset to 0 to reflect the breaking API changes

New: createFileUploader

Replaces the old single-request createFileUpload. Key differences:

  • Per-file state — each file in a batch gets its own request dispatched in parallel. Per-file progress, status, error, and response are tracked in a createStore array (files: readonly FileUploadEntry[]) for fine-grained JSX reactivity — only the changed row re-renders
  • Aggregate memosprogress and status are createMemo derivations across all entries (uploading > error > aborted > success > idle)
  • Tree-shakeable transportSendFunction is passed in explicitly; fileSender (XHR) is a separate named export so bundlers can drop it when unused
  • removeFile(fileName) and clearFiles() — remove entries from the store; both abort all in-flight uploads and bump a generation counter so stale async closures abandon their store writes
  • Abort support — each file gets its own AbortController; abort() cancels all; upload() called again cancels the previous batch

createFilePicker

  • Renamed from createFileUploader (which was a misnomer — it only selects files)
  • Added isLoading accessor: true while the selectFiles callback is pending
  • Added removeFile and clearFiles
  • Fixed JSX.EventHandler usage (removed from solid-js in 2.0) — handler now typed as (event: Event) => void

createDropzone

  • Fixed TypeScript errors: removed JSX.EventHandler<T, DragEvent> (no longer in solid-js) — all 7 handlers retyped as (event: DragEvent) => void, removing 14 as any casts
  • isServer import updated to @solidjs/web

fileUploader

  • Ported to Solid 2.0 ref-callback factory pattern (replaces the removed use: directive)

Summary by CodeRabbit

Release Notes – @solid-primitives/upload v0.2.0

  • New Features

    • Added createFilePicker() for native file selection with reactive state (files, error, isLoading)
    • Added fileSender() factory for XHR-based file uploads with progress tracking
    • createDropzone now exposes reactive error and isLoading state
  • Breaking Changes

    • Updated to Solid.js 2.0 beta.10
    • createFileUploader now requires an explicit SendFunction transport parameter
    • fileUploader is now a ref callback (ref={fileUploader(opts)}) instead of a directive

Review Change Stack

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 21, 2026

🦋 Changeset detected

Latest commit: cb3962a

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/upload 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

@davedbase davedbase changed the base branch from main to next April 21, 2026 01:47
@davedbase davedbase added this to the Solid 2.0 Upgrade milestone Apr 21, 2026
@davedbase davedbase marked this pull request as ready for review May 17, 2026 15:41
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2decb452-c009-46bd-81cf-02093aa025b2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/upload/dev/index.tsx (1)

89-103: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Demo is still using pre-migration picker calls.

Line 9, Line 10, and Line 47 still use createFileUploader as a picker (selectFiles flow). In this migration, picker behavior moved to createFilePicker, so this example is out of sync and can fail typing/usage validation.

Suggested fix
-import { createDropzone, createFileUploader, fileUploader } from "../src/index.js";
+import { createDropzone, createFilePicker, fileUploader } from "../src/index.js";

-const { files, selectFiles } = createFileUploader();
-const { files: filesAsync, selectFiles: selectFilesAsync } = createFileUploader();
+const { files, selectFiles } = createFilePicker();
+const { files: filesAsync, selectFiles: selectFilesAsync } = createFilePicker();

-const { files, selectFiles } = createFileUploader({
+const { files, selectFiles } = createFilePicker({
   multiple: true,
   accept: "image/*",
 });
🤖 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/upload/dev/index.tsx` around lines 89 - 103, The demo still uses the
old picker API (createFileUploader/selectFiles) causing type/usage mismatches;
update any picker usage to the new API by replacing calls to createFileUploader
with createFilePicker and adapt the directive/handler (e.g., fileUploader) to
call the createFilePicker/selectFiles flow and its new config keys (rename any
selectFiles-related callbacks or props to the createFilePicker equivalents) so
the ref={fileUploader(...)} invocation and the setFiles/userCallback wiring
match the new createFilePicker signature.
🤖 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 @.changeset/upload-solid-2-migration.md:
- Around line 5-10: Update the stale migration notes that reference Solid 2.0
beta.7 to match the shipped beta.10: change the header/version string "Solid.js
2.0 (beta.7)" and the two bullet items that mention `solid-js@^2.0.0-beta.7` and
`@solidjs/web@^2.0.0-beta.7` to `beta.10`, and verify the descriptions for
`isServer`, `createDropzone`, and `fileUploader` remain accurate for the beta.10
API surface so the migration guidance aligns with the published package
metadata.

In `@packages/upload/src/createDropzone.ts`:
- Around line 62-98: The isDragging state should be driven by drop-target
events, not source events: update the handlers so setIsDragging(true) is called
in onDragEnter and/or onDragOver, and setIsDragging(false) is called in
onDragLeave and onDrop (remove or stop relying on setIsDragging in
onDragStart/onDragEnd); locate the handlers named onDragStart, onDragEnd,
onDragEnter, onDragLeave, onDragOver and onDrop and move/adjust the
setIsDragging calls accordingly so external OS/file drags correctly toggle the
dragging UI.

In `@packages/upload/src/createFileUploader.ts`:
- Around line 69-99: The code calls the user-supplied send(...) directly inside
uploadFiles.map so a synchronous throw escapes the per-file rejection handler;
wrap the send call so sync throws are converted into a rejected Promise and
handled the same way as async rejections. Specifically, around the send(...)
invocation used in uploadFiles.map (which currently passes the progress callback
and controllers[i]!.signal and then chains .then(...) handlers), ensure you call
Promise.resolve().then(() => send(...)) or use a try/catch that returns
Promise.reject(err) so any synchronous exception flows into the existing
.then(success, error) branch and the setFiles updates (status, response, error)
for the corresponding index i still run when generation === thisGen.

In `@packages/upload/src/fileSender.ts`:
- Around line 58-61: The fileSender currently forwards all headers to
xhr.setRequestHeader which allows callers to override Content-Type and break
FormData multipart boundaries; update the header loop in fileSender so that if
the payload is a FormData (check via data instanceof FormData) you skip any
header whose name (case-insensitively) equals "content-type" and still set all
other headers via xhr.setRequestHeader; ensure the check uses the existing
headers variable and xhr so callers’ other headers remain intact while letting
the browser supply the correct multipart Content-Type.

In `@packages/upload/src/fileUploader.ts`:
- Around line 20-33: The onChange handler (function onChange) fails to reset the
file input value, so selecting the same file won't retrigger change events;
after calling transformFiles and before/after invoking userCallback (and
regardless of success or error), clear the input by setting target.value = ''
(or target.value = null) to reset the file input; ensure this happens even when
userCallback throws by putting the value-reset in a finally block or after the
try/catch so setFiles(parsedFiles), await userCallback(parsedFiles), the onError
handling, and then resetting target.value are executed in all paths.

In `@packages/upload/src/types.ts`:
- Around line 29-30: Change the removal API to use a stable unique identifier
instead of fileName: update the removeFile signature in types (and any matching
interfaces) from removeFile(fileName: string) => void to removeFile(fileId:
string) => void (or a typed FileId/handle), then update all
callers/implementations (createFilePicker, createDropzone, createFileUploader
and any other code referenced at the same locations) to assign and propagate a
stable id for each added file and perform removal by that id so duplicates are
disambiguated; ensure exported types and internal logic consistently use the new
id field and adapt tests/consumers accordingly.

In `@packages/upload/test/index.test.tsx`:
- Around line 397-401: The test creates a Solid root and immediately calls
dispose() before invoking the ref callback, which hides lifecycle/owner issues;
update the tests (instances using createRoot and the ref variable produced by
fileUploader) to call ref(input) while the root is still active and only call
dispose() afterwards—i.e., move the dispose() call to after invoking ref (and
any assertions) for the fileUploader ref usages at the shown locations so the
ref callback runs inside the active root and cleanup happens afterwards.
- Around line 804-817: The test starts a reactive root via createRoot and calls
upload(...) but never settles the mocked XHR nor disposes the root, causing
leaked state across tests; update the test that uses
makeMockXhr/createRoot/upload/makeUploadFile/fileSender to (1) resolve the
mocked XHR response (e.g., call the mock XHR's success handler or resolver such
as xhr.onload/xhr.resolve/xhr.respond) so the upload promise settles and then
await the upload call, and (2) call the returned dispose() (or use
upload(...).finally(() => dispose())) to tear down the reactive root; apply the
same change to the other test mentioned.

---

Outside diff comments:
In `@packages/upload/dev/index.tsx`:
- Around line 89-103: The demo still uses the old picker API
(createFileUploader/selectFiles) causing type/usage mismatches; update any
picker usage to the new API by replacing calls to createFileUploader with
createFilePicker and adapt the directive/handler (e.g., fileUploader) to call
the createFilePicker/selectFiles flow and its new config keys (rename any
selectFiles-related callbacks or props to the createFilePicker equivalents) so
the ref={fileUploader(...)} invocation and the setFiles/userCallback wiring
match the new createFilePicker signature.
🪄 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: 17ecaa19-43b8-4b8c-8045-507f1f1c147d

📥 Commits

Reviewing files that changed from the base of the PR and between 76d6f2e and cb3962a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • .changeset/upload-solid-2-migration.md
  • packages/upload/README.md
  • packages/upload/dev/index.tsx
  • packages/upload/package.json
  • packages/upload/src/createDropzone.ts
  • packages/upload/src/createFilePicker.ts
  • packages/upload/src/createFileUploader.ts
  • packages/upload/src/fileSender.ts
  • packages/upload/src/fileUploader.ts
  • packages/upload/src/index.ts
  • packages/upload/src/types.ts
  • packages/upload/test/index.test.tsx
  • packages/upload/tsconfig.json

Comment thread .changeset/upload-solid-2-migration.md
Comment thread packages/upload/src/createDropzone.ts
Comment thread packages/upload/src/createFileUploader.ts
Comment thread packages/upload/src/fileSender.ts
Comment thread packages/upload/src/fileUploader.ts
Comment thread packages/upload/src/types.ts
Comment thread packages/upload/test/index.test.tsx
Comment thread packages/upload/test/index.test.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant