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

> @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.

**react-component/upload** — 📤 Low-level React upload primitive for Ajax, drag, paste, directory, and custom requests.

- Repository: https://github.com/react-component/upload
- Website: https://react-component.github.io/upload
- Stars: 803 · Forks: 323
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/react-component-upload

## 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/upload@1.2.0 on 2026-09-11, @rc-component/upload@1.1.1 on 2026-05-26 and @rc-component/upload@1.1.0 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.

## 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.

## FAQ

### 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.

## Sources

- [License: MIT](https://github.com/react-component/upload/blob/master/LICENSE)
- [Project website](https://react-component.github.io/upload)
- [react-component/upload on GitHub](https://github.com/react-component/upload)
- [README](https://github.com/react-component/upload/blob/master/README.md)
- [Releases](https://github.com/react-component/upload/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/react-component-upload
