Library / SDK
react-component/upload avatar
react-component/upload

@rc-component/upload handles the transport, leaves the list to you

📤 Low-level React upload primitive for Ajax, drag, paste, directory, and custom requests.

803 stars323 forksTypeScriptMIT

At a glance

What is it?
@rc-component/upload is a headless React upload primitive from the Ant Design ecosystem: it owns the hidden file input, the Ajax request, progress events and the abort and retry handles, but it renders no file list. It suits engineers building a custom upload widget, not teams that want a finished component.
Who is it for?
Adopt @rc-component/upload when you are writing your own upload widget and want the request layer, progress events and abort and retry handles without inheriting somebody else's file list markup. Do not adopt it if you expect a ready made upload component, since the package deliberately ships none.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 39 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A transport layer with no file list attached

The package describes itself as a low-level React upload primitive for Ajax, drag, paste, directory and custom requests, and it sits in the Ant Design ecosystem. That framing sets expectations correctly. There is no list of uploaded files, no thumbnail, no progress bar, no cancel button rendered for you. What you get is the awkward half of an uploader that every project ends up rewriting: the hidden file input, the request lifecycle, the progress callback and the handles for cancelling and retrying. The user visible part is entirely your component. The people who benefit are engineers building an upload widget that has to match an existing design system, or a product that needs behaviour the ready made components do not expose, such as directory uploads, paste to upload, or per request credential and header control. If you only need a working upload box for an admin panel, this package asks you to build more than you wanted.

The request contract is a set of props, not an options object

Everything the transport layer can do arrives as props on the Upload component. The endpoint is action, which accepts either a string or a function that resolves to a string, so the URL can be computed per file. The HTTP verb comes from method and allows POST, PUT and PATCH in either case. name sets the form field name and defaults to file, data attaches extra values and can itself be a function of the file, headers is a map, and withCredentials controls whether credentials travel with the Ajax request. customRequest replaces the default request behaviour entirely, which is the escape hatch for anyone whose transport is not a plain form post. The lifecycle is reported through onStart, onProgress, onSuccess and onError, with onSuccess receiving the response, the file and the xhr object. A batch hook, onBatchStart, reports when a batch begins. That is a wide surface, and it is worth noticing that none of it is stateful: the package does not keep a list for you, so onProgress and onSuccess are your cues to maintain whatever collection your interface renders.

beforeUpload is where validation and transformation belong

beforeUpload is the single most consequential prop for a headless uploader, because it decides whether a request happens at all. Its signature accepts the file and the file list, and it may return a BeforeUploadFileType, a promise of that type or void, and the documentation states that returning false stops the upload. That single return value covers three different jobs: rejecting a file that fails a type or size rule, rewriting the file before it is sent, or rewriting the list. Because the hook can return a promise, an async check such as asking a server whether a name already exists is possible without blocking the interface. The rest of the flow is upstream of that decision, so the ordering matters: a file that returns false never reaches action, and therefore never produces onStart, onProgress or onSuccess. That is the mechanism to build a validation message around, and it is also a place where mistakes are quiet, because a rejected file simply produces no callbacks.

abort and retry are exposed through the component instance

Cancelling is not a prop here. The package exposes abort(file) through the component instance, and the method table also lists retry(file) for retrying an upload of a specific file. Both take a file object, the same object handed to onStart, onProgress and onSuccess, so the natural implementation is to keep the file reference from onStart in your own state and use it as the handle. The reason for this design is straightforward: the primitive holds no queue, so it cannot know which file a user meant when they press a cancel button in a list you drew. That is a fair trade, and it is also a cost. Every list row needs its own reference retained, and a component that unmounts mid upload leaves the request to the browser unless you call abort yourself. The documentation gives no guidance on unmount behaviour, so if your route changes while a large file is in flight, decide in your own code whether to abort or to let it continue.

Directory, drag and paste are flags, and the input is styled through slots

The input side of the component is a set of booleans and one structural choice. directory enables directory upload, pastable enables paste to upload, multiple allows selecting more than one file, and disabled turns off the trigger. openFileDialogOnClick defaults to true, so clicking the root of the component opens the file dialog; set it to false when your child already handles clicks. The related prop hasControlInside tells the component that the child already contains a control element, which is how the trigger is disabled for elements like a button without fighting the child's own behaviour. component chooses the root element and defaults to span, so you can render a div instead if you need block layout. Styling is done through className, style, and the semantic slots classNames and styles, which take an input key for the hidden input. The default prefixCls is rc-upload, so your class names will carry that prefix unless you change it.

Getting it into a project and rendering the first upload

Installation is a single package from npm:

bash
npm install @rc-component/upload

The minimal usage in the documentation wraps your own control as a child, points the component at an endpoint, and logs the two callbacks you will eventually replace:

ts
import Upload from '@rc-component/upload';

export default () => (
  <Upload
    action="/upload"
    onStart={file => {
      console.log('start', file.name);
    }}
    onSuccess={(response, file) => {
      console.log('success', file.name, response);
    }}
  >
    <button type="button">Upload</button>
  </Upload>
);

The child button is what the user clicks, and the component clones it into the trigger, so your markup stays yours. For local development the project ships a dumi site, started with the same package manager workflow:

bash
npm install
npm start

That serves the documentation site at http://localhost:8000, where the API table and the interactive examples live. The script list in package.json also covers the rest of the workflow, with npm test, npm run tsc for a type check, npm run compile for the package build through father, and npm run build for the docs bundle.

Building your own transport is the real alternative

There is a second option worth naming: skip the package and write the request yourself. A plain implementation is a form post built from a hidden input, a FormData body and a fetch call with a progress reader, which is a day of work for a single file and a week once you need cancellation, retries, directory selection and paste handling. The difference in approach is where the state lives. Your own code owns the queue, the progress map and the cancel logic, so the UI is trivial to write but so is every edge case around it. This package instead moves the transport into a tested implementation and leaves the collection of files in your component, which means less code for the request and more code for the list. Between the two, the deciding question is not feature coverage but how much of the list behaviour you want to control. The package also sits under the Ant Design ecosystem, so a team already shipping that family of components inherits its conventions, while a standalone team would need its own wrapper around either approach.

Release history shows steady maintenance on the master branch

The repository is not archived and the last push was on 2026-09-14. Three package releases are visible in the history: @rc-component/[email protected] on 2026-09-11, @rc-component/[email protected] on 2026-05-26 and @rc-component/[email protected] on 2025-10-31, and the version field in package.json reads 1.2.0. The default branch is master and the published package is scoped, so the import path is @rc-component/upload while the repository lives at react-component/upload. The package ships only the lib and es directories and depends on @rc-component/util and clsx, so the runtime footprint is small and the dependency surface is narrow. Licensing is MIT, which is the permissive choice you would expect for a primitive meant to be embedded in someone else's design system, though note that the ecosystem layer above it may carry its own terms. Upgrades are driven by the package version rather than by a breaking change policy you can read here, and the release script runs the build before the rc-np tool handles publishing.

Editorial conclusion

Adopt @rc-component/upload when you are writing your own upload widget and want the request layer, progress events and abort and retry handles without inheriting somebody else's file list markup. Do not adopt it if you expect a ready made upload component, since the package deliberately ships none. Check first that your server accepts the field name file, since that is the default and the documentation does not say it is configurable per request, and confirm that the abort and retry methods are reachable on your instance before you design the UI around them.

Frequently asked questions

Does @rc-component/upload render the list of uploaded files?

No. The package is described as a low-level primitive and it exposes callbacks such as onStart, onProgress, onSuccess and onError rather than a file list, so the collection of files and its rendering are the consumer's responsibility.

How do I cancel an upload in @rc-component/upload?

The component instance exposes abort(file), and the method table also lists retry(file) for a specific file. Both take the file object handed to onStart, so you keep that reference in your own state.

Can I change the upload endpoint or field name in @rc-component/upload?

The endpoint is set through action, which accepts a string or a function resolving to a string. The form field name comes from the name prop, whose default is file.

How do I run the local docs for @rc-component/upload?

The project ships a dumi site. Running npm install followed by npm start serves it at http://localhost:8000, and the same script list includes npm test, npm run tsc, npm run compile and npm run build.

Official sources

  1. License: MIT
  2. Project website
  3. react-component/upload on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/react-component-upload.svg)](https://hysenlabs.com/projects/react-component-upload)

Was this useful?