Upload

Files chosen from a picker or dropped onto a target, with a list of what is uploading, what succeeded and what failed.

Usage

When to use Upload, what each list type is for, and the decisions it leaves to you.

When to use

Upload is a trigger plus a list. It opens the file picker, hands each file to a request, and keeps one row per file carrying the name, a progress bar, a status and the actions (preview, download, remove). Use it when the reader needs to see that list.
  • Attachments on a form or a message, where the reader has to see what is attached before submitting.
  • An avatar or a cover image, previewed in place, with maxCount={1} so a second pick replaces the first.
  • A bulk import, where several files go at once and each one reports its own outcome.
  • Anywhere a failed file has to stay visible and retryable rather than being silently dropped.
Use something else when the list is not the point.
  • One file, submitted with the rest of the form, no per-file status: a plain <input type="file"> with a <label> is smaller, already accessible and needs no state.
  • The file never leaves the browser (a CSV parsed client-side, an image measured before it is drawn). Read it in beforeUpload and return Upload.LIST_IGNORE, or skip the component.
  • The screen needs a file manager (rename, move, folders, sorting). Build a table over your own API. Upload only knows about files it just sent.

Choosing a listType

listType decides what a file looks like in the list, and on the two card types it changes the trigger too. The default is text.
  • text: one row per file, a paperclip, the name, the actions at the end. Use it for documents and anything that is not an image, since a thumbnail of a PDF says nothing.
  • picture: the same row with a thumbnail in front of the name. A picked image gets a local thumbnail (previewFile), so it shows before the server answers.
  • picture-card: each file becomes a square tile in a grid, and the trigger becomes a dashed tile beside them. A plus glyph over a one-word label is the convention inside it.
  • picture-circle: the same tile, round. Use it for avatars. The circle crops the image, so a wide photo looks badly framed.
On the two card types the actions move into a hover overlay on the tile, in white over a dark scrim. The Accessibility tab covers what that means for actions that only show on hover.

Where the bytes go: action and customRequest

There are two ways to send a file, and you use one or the other. Give action a URL and Upload sends the request itself, with progress events. Pass customRequest to send it yourself, and nothing happens unless you call the callbacks it receives.
  • action takes a string, or a function of the file returning a string or a promise of one, so a per-file presigned URL is still the action path.
  • data, headers, method (default post), name (the form field, default file) and withCredentials are arguments to that XHR. They do nothing on the customRequest path.
  • customRequest receives { file, onProgress, onSuccess, onError }, and you call them. Forget onProgress and the bar never moves. Forget onSuccess and the row says “uploading” forever.
  • customRequest also gets a second argument with defaultRequest, so you can fetch a signed URL, adjust the options and then hand off to the built-in request.
  • Whatever onSuccess receives lands on file.response, which is where a server-issued id or URL comes from. A controlled list is what lets you write it back onto the row.

beforeUpload, and why accept is only a hint

beforeUpload runs once per file, before the request, and its return value decides three different things.
  • Return nothing, or true, and the file uploads.
  • Return false to skip the request and keep the file in the list. That is how you collect files and send them yourself later.
  • Return Upload.LIST_IGNORE to keep it out of the list as well. Use this for a rejected file.
  • Return a Promise of a File or Blob to transform the file first: compress, rename, watermark, strip EXIF. Rejecting that promise drops the file.
  • Say why. A size or type rejection with no message looks like a broken picker, so pair every LIST_IGNORE with a message.error or an Alert beside the field.
accept is a convenience for the reader. It does not stop a file on every route.
  • The string goes onto the hidden input's accept attribute, which only sets the OS dialog's default filter. Every desktop file dialog lets the reader switch back to “All files”.
  • Files chosen in the picker are not checked again. Dropped files and files from a directory are checked against accept.
  • accept also takes { format, filter }: filter: "native" turns that drop check off, and a predicate replaces it with your own.
  • Check type and size in beforeUpload, and again on the server.

Controlled fileList against uncontrolled

Pass defaultFileList and Upload owns the array. Pass fileList and you own it, which means you own all of it.
  • Controlled means writing back the array onChange hands you, info.fileList, including the status and percent Upload just put on it. Building your own array out of info.file is the most common Upload bug: rows stick at “uploading”, or a removed file reappears.
  • info.file is the one file that changed, and info.fileList is the whole list. Set state from the list, and branch on the file.
  • onChange fires on every step: start, each progress tick, done, error, remove. Anything expensive in the handler runs dozens of times per file, so run it only on info.file.status === "done".
  • A controlled list lets you rewrite a row: set url from the response so the preview link works, swap the name for a server id, or drop a failed file on the reader's behalf.
  • Uncontrolled is fine for a fire-and-forget attachment field. Switch to controlled once another part of the form needs to know what is attached.

multiple and maxCount

  • multiple (default false) only decides whether the OS dialog lets more than one file be selected at once. It does not cap the list: a reader can open the picker five times and add five files.
  • maxCount is the cap. Files past it are dropped, not queued.
  • With maxCount={1} a new file replaces the current one instead of being rejected, which suits an avatar field.
  • The trigger stays visible once the cap is reached. Hide it yourself when fileList.length hits the cap, and state the limit in the copy near it.
  • directory swaps the dialog for a folder picker and adds every file inside. Those files are checked against accept, since a folder brings everything in it.

Upload.Dragger

Upload.Dragger is the same component with type="drag": the trigger becomes a dashed drop area, and your children are what it shows inside.
  • Use it when dropping is the expected gesture: a bulk import, an editor, anything where the files are already in a window beside the browser.
  • The conventional contents are one large glyph, one line of instruction and one line of hint. Use the hint for the constraint (types, size, count).
  • height is Dragger's only extra prop. Nothing else on Upload takes it.
  • Dropping has no keyboard equivalent, so the area is still click-to-open, with a role and a tab stop on the area itself. The Accessibility tab has the detail.
  • Use one drop area per screen. With two, only the hover state tells the reader which one a drag is over.
  • pastable listens on the whole page. Pasting an image anywhere adds it, not only while the Upload has focus, so use it for one Upload per page.

Removing and previewing

  • onRemove runs before the row goes, and returning false, or a promise of false, cancels the removal. That is where a confirm goes, and where a DELETE to your own API goes.
  • Removing from the list does not remove from the server. Upload aborts a request in flight and does nothing else, so a finished file stays wherever you put it.
  • onPreview has no default behavior. Without it, clicking a file that carries a url follows the link in a new tab and a file without one does nothing at all. A modal holding the image is the usual implementation.
  • A preview needs url or thumbUrl on the file. Picture lists make a local one for picked images, but a file loaded from your own API needs its real URL on it.
  • showUploadList is the switch: false for no list at all, or a config object to turn the three actions on and off per file. The three glyphs come from your icon pack, and any icon you pass wins.
  • In picture lists, a file without a thumbnail shows the same file icon whether or not it is an image. Pass your own iconRender if they should differ.
  • itemRender replaces the whole row and hands you the actions as callbacks. Use it last: you take over the progress bar, the status styles and the error tooltip along with the layout.

When an upload fails

  • A failed file stays in the list with status: "error", painted red, with the reason in a tooltip. The reason is file.response if it is a string, then file.error.statusText, then file.error.message, and otherwise the locale's “Upload error”.
  • A JSON error body is not a string, so the reader gets the generic text. Return plain text on the action path, or take the request over and call onError with a real Error whose message says something.
  • Red ink plus a tooltip is a hover affordance and a color. Where the failure blocks the form, repeat the reason in text near the submit button.
  • There is no retry button. Picking the file again is the retry. A controlled list lets you offer one: keep originFileObj, drop the row and push it back.
  • When the whole batch fails (network down, token expired), say it once above the list, not once per file.