Contents
This is the main UI package. Its role is to initialize and expose the uploader functionality. It contains the Provider that all other UI packages rely on.
It provides multiple hooks that enable more advanced features and data for client apps.
The best place to get started is at our: React-Uploady Documentation Website
#Yarn:
$ yarn add @rpldy/uploady
#NPM:
$ npm i @rpldy/uploady
Name (* = mandatory) | Type | Default | Description |
---|---|---|---|
Uploader Options | |||
autoUpload | boolean | true | automatically upload files when they are added |
destination | Destination | undefined | configure the end-point to upload to |
inputFieldName | string | "file" | name (attribute) of the file input field (requires sendWithFormData = true) |
grouped | boolean | false | group multiple files in a single request |
maxGroupSize | number | 5 | maximum of files to group together in a single request |
formatGroupParamName | (number, string) => string | undefined | determine the upload request field name when more than file is grouped in a single upload |
fileFilter | (File | string, index: number, File[] | string[]) => boolean | undefined | return false to exclude from batch |
method | string | "POST" | HTTP method in upload request |
params | Object | undefined | collection of params to pass along with the upload (requires sendWithFormData = true) |
forceJsonResponse | boolean | false | parse server response as JSON even if no JSON content-type header received |
withCredentials | boolean | false | set XHR withCredentials to true |
enhancer | UploaderEnhancer | undefined | uploader enhancer function |
concurrent | boolean | false | issue multiple upload requests simultaneously |
maxConcurrent | number | 2 | maximum allowed simultaneous requests |
send | SendMethod | @rpldy/sender | how to send files to the server |
sendWithFormData | boolean | true | upload is sent as part of formdata - when true, additional params can be sent along with uploaded data |
formatServerResponse | FormatServerResponseMethod | undefined | function to create the batch item's uploadResponse from the raw xhr response |
clearPendingOnAdd | boolean | false | whether to clear pending batch(es) when a new one is added |
isSuccessfulCall | IsSuccessfulCall | undefined | callback to use to decide whether upload response is succssful or not |
fastAbortThreshold | number | 100 | the pending/active item count threshold from which to start using the performant abort mechanism |
userData | any | undefined | metadata set by the user and isn't used by the upload process in any way, provided as a convenience to pass data around |
Uploady Options | |||
debug | boolean | false | enable console logs from uploady packages |
listeners | Object | undefined | map of event name and event handler |
customInput | boolean | false | whether to use a custom file input (see: useFileInput |
inputFieldContainer | HTMLElement | document.body | html element to place the file input element inside |
children | React.Node | undefined | any part of your React app that will require access to the upload flow (components, hooks, etc.) |
capture | string | null | input/file#capture - affects file input only. for example, drag&drop or programmatic uploads will not be affected by this setting |
multiple | boolean | true | input/file#multiple - affects file input only. for example, drag&drop or programmatic uploads will not be affected by this setting |
accept | string | null | input/file#accept - affects file input only. for example, drag&drop or programmatic uploads will not be affected by this setting |
webkitdirectory | boolean | false | webkitdirectory - affects file input only. for example, drag&drop or programmatic uploads will not be affected by this setting |
fileInputId | string | undefined | the value to use for the internal file input element |
noPortal | boolean | false | Dont render Uploady's file input in a portal. (default: false) For SSR, noPortal = false causes a React warning in DEV. |
To be able to use one of the UI Components or one of the hooks, you need to wrap them with Uploady. This will give them access to the UploadyContext.
import Uploady from "@rpldy/uploady";
const App = () => (<Uploady
multiple
grouped
maxGroupSize={2}
method="PUT"
destination={{url: "https://my-server", headers: {"x-custom": "123"}}}>
<RestOfMyApp/>
</Uploady>)
When working in React, The UploadyContext is the API provider for the uploader mechanism. It wraps the uploader and exposes everything the app using it needs.
import React from "react";
import Uploady, { useUploady } from "@rpldy/uploady";
const MyComponent = () => {
const uploady = useUploady();
const onClick = ()=> {
uploady.showFileUpload();
};
return <button onClick={onClick}>Custom Upload Button</button>
};
const App = () => (<Uploady>
<MyComponent/>
</Uploady>);
(?UploadOptions) => void
Show the native file selection dialog. Optionally Pass options as a parameter to override options set as props on the Uploady component.
(files: UploadInfo | UploadInfo[], addOptions: ?UploadOptions) => void
Upload file(s). Optionally Pass options as the second parameter to override options set as props on the Uploady component.
(uploadOptions?: UploadOptions) => void
Start uploading batches that were added with autoUpload = false
Upload Options can be added here to be (deep) merged with the options the batch(es) was added with.
() => void
Remove all batches that were added with autoUpload = false, and were not uploaded yet.
(UploadOptions) => void
Update the uploader instance with different options than the ones used to initialize
() => UploadOptions
get the current options used by the uploader
(name: any) => ?Object
get an extension registered by that name (through an enhancer)
(id?: string) => void
abort all files being uploaded or a single item by its ID
(id: string) => void
abort a specific batch by its ID
(name: any, cb: EventCallback) => OffMethod
register for an event
(name: any, cb: EventCallback) => OffMethod
register once for an event
(name: any, cb?: EventCallback) => void
unregister an event handler
Uploady provides hooks for all uploader events, as well as a few other useful ones.
Called when a new batch is added.
This event is cancellable
import { useBatchAddListener } from "@rpldy/uploady";
const MyComponent = () => {
useBatchAddListener((batch, options) => {
console.log(`batch ${batch.id} was just added with ${batch.items.length} items`);
});
//...
};
Called when batch items start uploading
This event is cancellable
This event can be scoped to a specific batch by passing the batch id as a second parameter
The callback function can be
async
or return a promise when needed
import { useBatchStartListener } from "@rpldy/uploady";
const MyComponent = () => {
useBatchStartListener((batch) => {
console.log(`batch ${batch.id} started uploading`);
});
//or scoped:
useBatchStartListener((batch) => {
console.log(`batch ${batch.id} started uploading`);
}, "b-123");
//...
};
The callback passed to the hook may also return an object containing items and/or options in order to update the request dynamically, similar to useRequestPreSend only for the entire batch. See withBatchStartUpdate HOC below for more details.
Called every time progress data is received from the upload request(s)
This event can be scoped to a specific batch by passing the batch id as a second parameter
import { useBatchProgressListener } from "@rpldy/uploady";
const MyComponent = () => {
const batch = useBatchProgressListener((batch) => {});
console.log(`batch ${batch.id} is ${batch.completed}% done and ${batch.loaded} bytes uploaded`)
//...
};
Scoping to an id can be done like so:
//...
const { completed: batchCompleted } = useBatchProgressListener("batch-id") || { completed: 0 };
//...
Called when batch items finished uploading
This event can be scoped to a specific batch by passing the batch id as a second parameter
import { useBatchFinishListener } from "@rpldy/uploady";
const MyComponent = () => {
useBatchFinishListener((batch) => {
console.log(`batch ${batch.id} finished uploading`);
});
//...
};
Called in case batch was cancelled from BATCH_START event handler
This event can be scoped to a specific batch by passing the batch id as a second parameter
import { useBatchCancelledListener } from "@rpldy/uploady";
const MyComponent = () => {
useBatchCancelledListener((batch) => {
console.log(`batch ${batch.id} was cancelled`);
});
//...
};
Called in case abortBatch was called
This event can be scoped to a specific batch by passing the batch id as a second parameter
import { useBatchAbortListener } from "@rpldy/uploady";
const MyComponent = () => {
useBatchAbortListener((batch) => {
console.log(`batch ${batch.id} was aborted`);
});
//...
};
Called in case batch failed with an error. These errors will most likely occur due to invalid event handling. For instance, by a handler (ex: BATCH_START) throwing an error.
This event can be scoped to a specific batch by passing the batch id as a second parameter
import { useBatchErrorListener } from "@rpldy/uploady";
const MyComponent = () => {
useBatchErrorListener((batch) => {
console.log(`batch ${batch.id} had an error: ${batch.additionalInfo}`);
});
//...
};
Called for batch when all its items have finished uploading or in case the batch was cancelled(abort) or had an error
This event can be scoped to a specific batch by passing the batch id as a second parameter
import { useBatchFinalizeListener } from "@rpldy/uploady";
const MyComponent = () => {
useBatchFinalizeListener((batch) => {
console.log(`batch ${batch.id} finished uploading with status: ${batch.state}`);
});
//...
};
Called when item starts uploading (just before) For grouped uploads (multiple files in same xhr request) ITEM_START is triggered for each item separately
This event is cancellable
This event can be scoped to a specific item by passing the item id as a second parameter
The callback function can be
async
or return a promise when needed
import { useItemStartListener } from "@rpldy/uploady";
const MyComponent = () => {
useItemStartListener((item) => {
console.log(`item ${item.id} started uploading`);
});
//or scoped:
useItemStartListener((item) => {
console.log(`item ${item.id} started uploading`);
}, "i-123");
//...
};
Called when item finished uploading
This event can be scoped to a specific item by passing the item id as a second parameter
import { useItemFinishListener } from "@rpldy/uploady";
const MyComponent = () => {
useItemFinishListener((item) => {
console.log(`item ${item.id} finished uploading, response was: `, item.uploadResponse, item.uploadStatus);
});
//...
};
Called every time progress data is received for this file upload
This event can be scoped to a specific item by passing the item id as a second parameter
import { useItemProgressListener } from "@rpldy/uploady";
const MyComponent = () => {
const item = useItemProgressListener((item) => {
//callback is optional for this hook
});
console.log(`item ${item.id} is ${item.completed}% done and ${item.loaded} bytes uploaded`)
//...
};
Scoping to an id can be done like so:
//...
const { completed: itemCompleted } = useItemProgressListener("item-id") || { completed: 0 };
//...
Called in case item was cancelled from ITEM_START event handler
This event can be scoped to a specific item by passing the item id as a second parameter
import { useItemCancelListener } from "@rpldy/uploady";
const MyComponent = () => {
useItemCancelListener((item) => {
console.log(`item ${item.id} was cancelled`);
});
//...
};
Called in case item upload failed
This event can be scoped to a specific item by passing the item id as a second parameter
import { useItemErrorListener } from "@rpldy/uploady";
const MyComponent = () => {
useItemErrorListener((item) => {
console.log(`item ${item.id} failed - `, item.uploadResponse);
});
//...
};
Called in case abort was called for an item
This event can be scoped to a specific item by passing the item id as a second parameter
import { useItemAbortListener } from "@rpldy/uploady";
const MyComponent = () => {
useItemAbortListener((item) => {
console.log(`item ${item.id} was aborted`);
});
//...
};
Called for item when uploading is done due to: finished, error, cancel or abort
This event can be scoped to a specific item by passing the item id as a second parameter
import { useItemFinalizeListener } from "@rpldy/uploady";
const MyComponent = () => {
useItemFinalizeListener((item) => {
console.log(`item ${item.id} is done with state: ${item.state}`);
});
//...
};
Called before a group of items is going to be uploaded Group will contain a single item unless "grouped" option is set to true.
Handler receives the item(s) in the group and the upload options that were used. The handler can change data inside the items and in the options by returning different data than received. See simple example below or this more detailed guide.
This event is cancellable
import { useRequestPreSend } from "@rpldy/uploady";
const MyComponent = () => {
useRequestPreSend(({ items, options }) => {
let method = options.method;
if (options.destination.url.startsWith("https://put-server")) {
method = "PUT";
}
return {
options: { method } //will be merged with the rest of the options
};
});
//...
};
Called in case abort was called for all running uploads
import { useAllAbortListener } from "@rpldy/uploady";
const MyComponent = () => {
useAllAbortListener(() => {
console.log("abort all was called");
});
};
Shortcut hook to get the Uploady Context instance
Will throw in case used outside of Uploady render tree
import { useUploady } from "@rpldy/uploady";
const MyComponent = () => {
const uploady = useUploady();
const onClick = () => {
uploady.showFileUpload();
}
//...
};
const App = () => (
<Uploady destination={{...}}>
<MyComponent/>
</Uploady>
);
Shortcut hook to set/get upload options.
import { useUploadOptions } from "@rpldy/uploady";
const MyComponent = () => {
const options = useUploadOptions({ grouped: true, maxGroupSize: 3 });
//...
};
Returns abort item method
import { useAbortItem } from "@rpldy/uploady";
const MyComponent = () => {
const abortItem = useAbortItem();
return <button onClick={() => abortItem("i-123")}>Abort Item</button>
};
Returns abort batch method
import { useAbortBatch } from "@rpldy/uploady";
const MyComponent = () => {
const abortBatch = useAbortBatch();
return <button onClick={() => abortBatch("b-123")}>Abort Batch</button>
};
Returns abort all method
import { useAbortAll } from "@rpldy/uploady";
const MyComponent = () => {
const abortAll = useAbortAll();
return <button onClick={() => abortAll()}>Abort All</button>
};
When customInput prop is set to true, Uploady will not create its own file input element. In this case, Uploady will wait for a ref to an existing input.
The way you pass in your own input element is by using this hook.
In case Uploady wasn't provided with a destination prop or if it doesn't have a URL property, Uploady will check whether the input resides in a form. It will then use the form's action and method to set the upload endpoint and request method.
In case the form's attributes were used for the upload destination, updating the form's attributes dynamically won't affect the uploader configuration once it was set.
import Uploady, { useFileInput } from "@rpldy/uploady";
import UploadButton from "@rpldy/upload-button";
const MyForm = () => {
const inputRef = useRef();
useFileInput(inputRef);
return <form action="/upload" method="POST">
<input type="file" name="testFile" style={{ display: "none" }} ref={inputRef}/>
</form>;
};
export const WithCustomFileInputAndForm = () => {
return <section>
<Uploady
debug
customInput
>
<MyForm />
<UploadButton/>
</Uploady>
</section>
};
This hook can also be used to retrieve Uploady's internal file input. Calling the hook without parameters will return the ref.
const inputRef = useFileInput();
if (inputRef.current) {
inputRef.current.setAttribute("webkitdirectory", "true");
}
NOTE! This isn't the recommended, or the 'Reacty' way to do things. It is still recommended to pass along a ref to an input that you render. In the future, accessing the internal input may have other consequences related to opting to interact with it directly instead of passing props to the Uploady component.
Check out the Custom Input guide for more details and examples.
HOC to enable components to interact with the upload data and options just-in-time before the request is sent. This is a hatch point to introduce custom logic that may affect the upload data.
A good example use-case for this is applying crop to selected image before it is uploaded.
When rendering the HOC's output, the id of the batch-item must be provided as a prop. This ensures the HOC only re-renders for a specific item and not for all. The id of the batch-item can be obtained from a hook (ex: useItemStartListener or useBatchStartListener)
import React, { useState, useCallback } from "react";
import Cropper from "react-easy-crop";
import Uploady, { withRequestPreSendUpdate } from "@rpldy/uploady";
import UploadButton from "@rpldy/upload-button";
import cropImage from "./my-image-crop-code";
const ItemCrop = withRequestPreSendUpdate((props) => {
const [crop, setCrop] = useState({ x: 0, y: 0 });
const [cropPixels, setCropPixels] = useState(null);
const { url, updateRequest, requestData } = props;
const onUploadCrop = useCallback(async() => {
if (updateRequest && cropPixels) {
//replace the file data with the cropped result
requestData.items[0].file = await cropImage(url, requestData.items[0].file, cropPixels);
//resume the upload flow with the updated file data
updateRequest({ items: requestData.items });
}
}, [url, requestData, updateRequest, cropPixels]);
const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
setCropPixels(croppedAreaPixels);
}, []);
return <>
<Cropper
image={url}
crop={crop}
onCropChange={setCrop}
onCropComplete={onCropComplete}
/>
<button style={{ display: updateRequest && cropPixels ? "block" : "none" }}
onClick={onUploadCrop}>
Upload Cropped
</button>
</>;
});
const MyApp = () => {
return <Uploady destination={{ url: "my-server.com/upload" }}>
<UploadButton />
<ItemCrop id="batch-item-1" />
</Uploady>
}
See the Crop Guide for a full example.
HOC to enable components to interact with the upload data and options of the batch just-in-time before the items are processed and requests are being sent.
This makes it possible to create a UI that will allow the user to interact and possible make changes to different or all items within the batch before a single request is made. For example: cropping multiple items prior to upload.
When rendering the HOC's output, the id of the batch must be provided as a prop. The id of the batch can be obtained from the useBatchAddListener
import React, { useState, useCallback } from "react";
import Cropper from "react-easy-crop";
import Uploady, { withBatchStartUpdate } from "@rpldy/uploady";
import UploadButton from "@rpldy/upload-button";
import UploadPreview from "@rpldy/upload-preview";
import cropImage from "./my-image-crop-code";
const CropperForMultiCrop = ({ item, url, setCropForItem }) => {
const [crop, setCrop] = useState({ x: 0, y: 0 });
const [cropPixels, setCropPixels] = useState(null);
const onSaveCrop = async () => {
const cropped = await cropImage(url, item.file, cropPixels);
setCropForItem(item.id, cropped);
};
const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
setCropPixels(croppedAreaPixels);
}, []);
return (<div>
<Cropper
image={url}
crop={crop}
onCropChange={setCrop}
onCropComplete={onCropComplete}
/>
{cropPixels &&
<Button onClick={onSaveCrop} id="save-crop-btn">Save Crop</Button>}
</div>);
};
const BatchCrop = withBatchStartUpdate((props) => {
const { id, updateRequest, requestData } = props;
const [cropped, setCropped] = useState({});
const hasData = !!(id && requestData);
const setCropForItem = (id, data) => {
setCropped((cropped) => ({ ...cropped, [id]: data }));
};
const onUploadAll = () => {
if (updateRequest) {
const readyItems = requestData.items
.map((item) => {
item.file = cropped[item.id] || item.file;
return item;
});
//update the items in the batch with the cropped files
updateRequest({ items: readyItems });
}
};
const getPreviewCompProps = useCallback((item) => {
return ({
onPreviewSelected: setSelected,
isCroppedSet: cropped[item.id],
});
}, [cropped, setSelected]);
return (<div>
{hasData &&
<button onClick={onUploadAll}>Upload All</button>}
<UploadPreview
rememberPreviousBatches
PreviewComponent={ItemPreviewThumb}
fallbackUrl="https://icon-library.net/images/image-placeholder-icon/image-placeholder-icon-6.jpg"
previewComponentProps={getPreviewCompProps}
/>
{selectedItem && hasData &&
<CropperForMultiCrop
{...selected}
item={selectedItem}
setCropForItem={setCropForItem}
/>}
</div>);
});
const MultiCropQueue = () => {
const [currentBatch, setCurrentBatch] = useState(null);
useBatchAddListener((batch) => setCurrentBatch(batch.id));
return <BatchCrop id={currentBatch} />;
};
export const MyApp = () => {
return <Uploady destination={{ url: "my-server.com/upload" }}>
<UploadButton />
<MultiCropQueue />
</Uploady>;
};
See the Multi Crop Guide for a full example.
Show Uploady your support by giving us a ⭐.
If you'd like to help Uploady grow & improve, take a look at the Contributing doc.
The Discussions page is a great place to ask questions, raise ideas and interact with Uploady maintainer, users and contributors.
Already using Uploady in Production? Let us know how & where in this open discussion.
Want to help sustain and grow Uploday? You can become a financial backer on OpenCollective.
Become a financial contributor and help us sustain our community.
You can make a one-time contribution or on a monthly basis