remix: [Bug]: File Upload API is incomplete/not working

Which Remix packages are impacted?

  • remix (Remix core)
  • create-remix
  • @remix-run/architect
  • @remix-run/cloudflare-workers
  • @remix-run/dev
  • @remix-run/express
  • @remix-run/netlify
  • @remix-run/node
  • @remix-run/react
  • @remix-run/serve
  • @remix-run/server-runtime
  • @remix-run/vercel

What version of Remix are you using?

1.1.1

What version of Node are you using? Minimum supported version is 14.

14

Steps to Reproduce

The parseMultipartFormData, createMemoryUploadHandler, and createFileUploadHandler are all still behind the unstable_ prefix. If you attempt to use them in their current state, the route with that action throws an error.

I have created a StackBlitz showcasing this issue, which can be found here: https://stackblitz.com/edit/node-wg6zks?file=app/routes/index.tsx

Expected Behavior

The parseMultipartFormData, createMemoryUploadHandler, and createFileUploadHandler methods are not behind the unstable_ prefix, and you are able to upload files and access them inside of the route’s action.

Actual Behavior

Using any of the unstable methods causes the page to error out with the following:

TypeError: Cannot read properties of undefined (reading 'root')
    at RemixRoute (https://node-wg6zks--3000.local.webcontainer.io/build/_shared/chunk-TKYDUCAE.js:4711:19)
    at renderWithHooks (https://node-wg6zks--3000.local.webcontainer.io/build/entry.client-LCRSCB77.js:11068:26)
    at mountIndeterminateComponent (https://node-wg6zks--3000.local.webcontainer.io/build/entry.client-LCRSCB77.js:13188:21)
    at beginWork (https://node-wg6zks--3000.local.webcontainer.io/build/entry.client-LCRSCB77.js:13975:22)
    at HTMLUnknownElement.callCallback2 (https://node-wg6zks--3000.local.webcontainer.io/build/entry.client-LCRSCB77.js:3678:22)
    at Object.invokeGuardedCallbackDev (https://node-wg6zks--3000.local.webcontainer.io/build/entry.client-LCRSCB77.js:3703:24)
    at invokeGuardedCallback (https://node-wg6zks--3000.local.webcontainer.io/build/entry.client-LCRSCB77.js:3737:39)
    at beginWork$1 (https://node-wg6zks--3000.local.webcontainer.io/build/entry.client-LCRSCB77.js:17084:15)
    at performUnitOfWork (https://node-wg6zks--3000.local.webcontainer.io/build/entry.client-LCRSCB77.js:16315:20)
    at workLoopSync (https://node-wg6zks--3000.local.webcontainer.io/build/entry.client-LCRSCB77.js:16266:13)

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 9
  • Comments: 18 (11 by maintainers)

Most upvoted comments

Following up on this.

I got it to work by moving the unstable_ createMemoryUploadHandler into the ActionFunction. I am following the pseudo-documentation laid out by @kentcdodds here: https://github.com/remix-run/remix/pull/383#issuecomment-972373927

I am not sure if this API is still supposed to be unstable_ or not, though, so leaving this open so that it can either be closed or resolved.

Perhaps (most probably?) they are aware of the issue. But having it on the agenda – that is having a pending PR? – wouldn’t be a waste of time, isn’t it? Unfortunately, I’m not comfortable with the PR process/flow…

I think they are aware, the problem is I don’t think this is the actual solution. I feel like there’s more issues down the line, as for instance .clone() doesn’t work when trying to handle the request before the file which leads me to the biggest issue:

I haven’t been able to handle the request and do the upload based on the request data. Currently I have to do the upload first and then guess what to do with with the file…

@joms, Object.fromEntries is probably the culprit there since all the files have the same name/key. FormData supports multiple values for a single key, but Object.fromEntries will just take the last one. Does it work if you do this?

You are absolutely right! That totally did the trick. I know I at some point thought for myself that I should use uploadBody.getAll and then the brain just went “nah, go do it with Object.fromEntries in stead!” 🤦

I’m going to implement image uploading as well. What is the current status of the situation explained above? Did the patch work in your production environment? Any feedback from your experience I should take into account? 🙏

I don’t know how prioritised this problem is at the time. Currently I am using a patch in production which works just fine for me. Looking forward to the day this actually is solved in Remix though.

patches/@remix-run+server-runtime+1.1.1.patch

diff --git a/node_modules/@remix-run/server-runtime/data.js b/node_modules/@remix-run/server-runtime/data.js
index 5f1fe2e..60f1b3b 100644
--- a/node_modules/@remix-run/server-runtime/data.js
+++ b/node_modules/@remix-run/server-runtime/data.js
@@ -34,7 +34,7 @@ async function callRouteAction({
 
   try {
     result = await action({
-      request: stripDataParam(stripIndexParam(request.clone())),
+      request: stripDataParam(stripIndexParam(request)),
       context: loadContext,
       params: match.params
     });

I’m going to implement image uploading as well. What is the current status of the situation explained above? Did the patch work in your production environment? Any feedback from your experience I should take into account? 🙏

@benwis I’ll add that the UploadHandler type is not exported from Remix as far as I can tell.

Got a PR up for that https://github.com/remix-run/remix/pull/1133

I’m having issues uploading larger images. After searching on the Discord server, I found this comment from cmwoodall:

I’ve been having the same issue when attempting to upload files… it hangs after the first chunk for files that are greater than one chunk in size. I actually had file uploads working fine in 1.0.6 by backporting the server bits (processMultipartRequest, UploadHandler, etc) and thus figured something else must have changed in the most recent release. After a bit of debugging I think I’ve found the source of the problem – a new request.clone() call that happens with callRouteAction() (https://github.com/remix-run/remix/blob/main/packages/remix-server-runtime/data.ts#L38). When I comment the request.clone() and pass along the original request everything seems to work great. Not my area of expertise but apparently cloning requests in node-fetch can cause issues (https://github.com/node-fetch/node-fetch#custom-highwatermark) and it looks like the highWaterMark is being set earlier in the request but perhaps doesn’t affect subsequent clones 🤷‍♂️

Here’s a link to the comment: https://discord.com/channels/770287896669978684/921148525948067850/921926697262141460