@rpldy/uploady
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.0 • Public • Published
    npm version circleci status codecov status bundlephobia badge rpldy storybook
    Contents

    Uploady

    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

    Installation

    #Yarn: 
       $ yarn add @rpldy/uploady
    
    #NPM:
       $ npm i @rpldy/uploady

    Props

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

    Example

    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>)

    Context

    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, { useUpload
        y } 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>);

    UploadyContext API

    showFileUpload

    (?UploadOptions) => void

    Show the native file selection dialog. Optionally Pass options as a parameter to override options set as props on the Uploady component.

    upload

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

    processPending

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

    clearPending

    () => void

    Remove all batches that were added with autoUpload = false, and were not uploaded yet.

    setOptions

    (UploadOptions) => void

    Update the uploader instance with different options than the ones used to initialize

    getOptions

    () => UploadOptions

    get the current options used by the uploader

    getExtension

    (name: any) => ?Object

    get an extension registered by that name (through an enhancer)

    abort

    (id?: string) => void

    abort all files being uploaded or a single item by its ID

    abortBatch

    (id: string) => void

    abort a specific batch by its ID

    on

    (name: any, cb: EventCallback) => OffMethod

    register for an event

    once

    (name: any, cb: EventCallback) => OffMethod

    register once for an event

    off

    (name: any, cb?: EventCallback) => void

    unregister an event handler

    Hooks

    Uploady provides hooks for all uploader events, as well as a few other useful ones.

    useBatchAddListener (event hook)

    Called when a new batch is added.

    This event is cancellable

        import { useBatchAddListener } from "@rpldy/uploady";
    
        const MyComponent = () => {
            useBatchAddListener((batch) => {
                console.log(`batch ${batch.id} was just added with ${batch.items.length} items`);  
            });
    
            //...    
        };

    useBatchStartListener (event hook)

    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

        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.

    useBatchProgressListener (event hook)

    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 };
        //...

    useBatchFinishListener (event hook)

    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`);  
            });
    
            //...    
        };

    useBatchCancelledListener (event hook)

    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`);  
            });
    
            //...    
        };

    useBatchAbortListener (event hook)

    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`);  
            });
    
            //...    
        };

    useBatchErrorListener (event hook)

    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}`);  
            });
    
            //...    
        };

    useBatchFinalizeListener (event hook)

    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}`);  
            });
    
            //...    
        };

    useItemStartListener (event hook)

    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

        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");
    
            //...    
        };

    useItemFinishListener (event hook)

    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);  
            });
    
            //...    
        };

    useItemProgressListener (event hook)

    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 };
        //...

    useItemCancelListener (event hook)

    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`);  
            });
    
            //...    
        };

    useItemErrorListener (event hook)

    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);  
            });
    
            //...    
        };

    useItemAbortListener (event hook)

    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`);  
            });
    
            //...    
        };

    useItemFinalizeListener (event hook)

    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}`);  
            });
    
            //...    
        };

    useRequestPreSend (event hook)

    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 
                };  
            });
    
            //...    
        };

    useAllAbortListener (event hook)

    Called in case abort was called for all running uploads

        import { useAllAbortListener } from "@rpldy/uploady";
    
        const MyComponent = () => {
            useAllAbortListener(() => {
                console.log("abort all was called");
            });
        };

    useUploadyContext (alias: useUploady)

    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>
        );

    useUploadOptions

    Shortcut hook to set/get upload options.

        import { useUploadOptions } from "@rpldy/uploady";
    
        const MyComponent = () => {
            const options = useUploadOptions({ grouped: true, maxGroupSize: 3 });
            
            //...       
        };

    useAbortItem

    Returns abort item method

        import { useAbortItem } from "@rpldy/uploady";
        
        const MyComponent = () => {
            const abortItem = useAbortItem();
            
            return <button onClick={() => abortItem("i-123")}>Abort Item</button>       
        };

    useAbortBatch

    Returns abort batch method

        import { useAbortBatch } from "@rpldy/uploady";
        
        const MyComponent = () => {
            const abortBatch = useAbortBatch();
            
            return <button onClick={() => abortBatch("b-123")}>Abort Batch</button>       
        };

    useAbortAll

    Returns abort all method

      import { useAbortAll } from "@rpldy/uploady";
      
      const MyComponent = () => {
          const abortAll = useAbortAll();
          
          return <button onClick={() => abortAll()}>Abort All</button>       
      };

    useFileInput

    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.

    HOCs

    withRequestPreSendUpdate

    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.

    withBatchStartUpdate

    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.

    Contribute

    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.

    Financial Contributors

    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

    Install

    npm i @rpldy/uploady

    DownloadsWeekly Downloads

    4,174

    Version

    1.0.0

    License

    MIT

    Unpacked Size

    199 kB

    Total Files

    16

    Last publish

    Collaborators

    • yoavniran