Folder Uploads in a React App

Posted on | Last updated on
Folder Uploads in a React App

Folder uploads in a React app arrive with v7 by way of filestack-js v4, which v7 can reach because it stopped bundling its own copy of that package. Enabling directory selection is a picker option. What actually changes is your interface, because a hundred files fail differently from one.

The folder upload example in filestack-snippets is the working version of everything below.

Folder support arrived with the React SDK v7.0.0 release.

Key takeaways

  • Folder support comes from filestack-js v4, so install it yourself as a peer dependency.
  • Raise maxFiles and turn on allowManualRetry, because a hundred files will drop something.
  • Read filesFailed, since partial success is the normal outcome at this scale.
  • The result is a flat list, so directory structure is your application’s concept to store.
  • Folder selection suits a one-off import, not ongoing work or per-file metadata.

Enabling it

npm install filestack-react@^7.0.1 filestack-js@^4.0.1

The second install is the part that matters here. In v6 the React package pinned its own filestack-js, so you got whatever version it shipped with. As a peer dependency you control it, and folder support comes from v4.

Then the picker:

<PickerOverlay
  pickerOptions={{
    fromSources: ['local_file_system'],
    maxFiles: 100,
    allowManualRetry: true,
  }}
  onUploadDone={(result: PickerResponse) => setUploaded(result.filesUploaded)}
/>

fromSources limited to the local file system is deliberate. Cloud sources have their own folder semantics and mixing them into a flow designed around directory selection confuses the interface more than it helps.

maxFiles needs raising from whatever your single-file default is. It is the cap on the whole selection, and a directory of photographs will exceed a default of five immediately.

The picker offering Select Folders To Upload for directory selection
The picker offering Select Folders To Upload for directory selection

 

Why allowManualRetry matters more here

Partial failure is the recurring theme of React file upload problems.

On a single file, a failed upload is a retry the user performs by clicking the button again. On a hundred files it is not, because clicking again means re-selecting the directory and re-uploading the ninety-seven that already succeeded.

allowManualRetry gives the picker its own retry affordance for the files that failed, which keeps the successful transfers. On a folder upload this stops being a nicety. A hundred files over a home connection will drop something, and without it the only recovery available to the user is starting over.

The result shape has not changed

onUploadDone still receives one PickerResponse with two arrays.

interface PickerResponse {
  filesUploaded: PickerFileMetadata[];
  filesFailed: PickerFileMetadata[];
}

With one file, filesFailed is almost always empty, and code that ignores it works for years. With a hundred, partial success is the normal case rather than the exception. A handler that reads only filesUploaded will report success while silently dropping the files that did not make it.

const onUploadDone = (result: PickerResponse) => {
  setUploaded(result.filesUploaded);
  setFailed(result.filesFailed);      // not optional at this scale
};

If your interface has one success state and one error state, folder uploads need a third for the case where some files landed and some did not. That is the real work in this feature, and it is in your components rather than in the picker.

Join the Filestack developer community on Discord

Preserving the directory structure

The picker returns a flat list. Each PickerFileMetadata carries a filename and a handle, and the folder a file came from is not part of the storage path unless you make it so.

If the structure matters, capture it as you handle the result and store it alongside the handles in your own database. Filestack stores files by handle, so the hierarchy is your application’s concept rather than the storage layer’s, and trying to encode it in filenames tends to produce collisions the first time two directories contain an index.html.

For most applications the flat list is what you want anyway. An import that reads a folder of CSVs does not care which subdirectory each came from.

What to warn the user about before they start

Three things belong in the interface before the dialog opens.

The file limit. maxFiles produces a rejection at selection time, so state the number in the interface rather than letting the picker report it after a directory is chosen.

That subdirectories are included. Directory selection is recursive, so a parent folder brings everything nested inside it.

That it takes time. Two hundred files take noticeably longer than one, and an upload abandoned partway leaves a partial set in storage.

Size limits and cost

Each file is still subject to whatever limits apply to a single upload, so a folder does not bypass a size cap. Every file in the selection counts individually against your plan’s file and storage allowance.

Setting maxSize in pickerOptions alongside maxFiles is a sensible guard, since it stops a single unexpected video inside an image directory from consuming a disproportionate share. The allowances each plan carries are listed on the Filestack pricing page.

Designing the progress state

A folder upload needs a progress indication that a single upload does not, and the useful distinction is between how far along the batch is and how far along the current file is.

Batch progress is the readable number. Forty of two hundred files gives a position and a rate. Per-file progress carries little in a folder upload, because the bar for each file appears and disappears too quickly to follow.

The picker shows its own progress interface, so for most applications this is already handled and the work is in what your page does around it. The state worth keeping in your own component is the count, because that is what the rest of the interface reacts to: a disabled submit button while a batch is in flight, a summary line afterwards, a list that grows as handles arrive.

The one thing to avoid is a spinner with no number. On a two-file upload it is fine. On two hundred it gives no way to distinguish a slow upload from a stuck one. When individual files in the batch are large, pausing and resuming large uploads covers the chunking underneath.

When a folder upload is the wrong shape

Directory selection suits an import: a one-off or occasional bulk action where the user has files on disk already organised the way they want them.

It suits ongoing work much less well. A media library that people add to a few files at a time is better served by a normal picker, because folder selection asks the user to organise their local disk around your application. A file dialog restricted to a directory is also a clumsier way to select four specific files than selecting four files.

The other case where it fits badly is anything requiring per-file metadata. If each upload needs a title, a category or a description, a hundred files means a hundred forms, and the interface problem that creates is larger than the upload problem you solved. Either collect the metadata afterwards in a table, or accept that this flow is for files that do not need any.

Testing it

Build a directory with a known shape and use it every time. Twenty files, one nested subdirectory, one file larger than your maxSize, and one with a name containing a space and a non-ASCII character.

Filenames arrive as the operating system recorded them, so an interface that renders them without escaping breaks on the first name carrying a space or a non-ASCII character.

Then confirm the partial-failure path by setting maxSize low enough that some files are rejected. If your interface reports unqualified success, the filesFailed array is not being read.

After the upload

Each file returns a handle, and the handles behave exactly as they do from a single upload. Transformations are path segments in front of them, applied across a list rather than one at a time.

Giving the user the folder back as a single download is the natural next step, and generating a zip from selected files turns a list of handles into one archive.

FAQ

Why do I need to install filestack-js separately?

Because v7 treats it as a peer dependency rather than bundling its own copy. That is what lets you pick the version, and folder support arrives with v4.

Does the upload preserve my folder structure?

No. The picker returns a flat list of handles, so the hierarchy is your application’s concept. Capture it when you handle the result and store it alongside the handles rather than trying to encode it in filenames.

Why does my interface report success when some files failed?

Because it is reading only filesUploaded. With one file that is almost always safe; with a hundred, partial success is the normal outcome, so filesFailed has to be handled as its own state.

Is folder upload the right choice for my media library?

Probably not. It suits a one-off bulk import where the files are already organised on disk. For ongoing work, or anything needing per-file titles and categories, a normal picker is the better fit.

 

 

Read More →