Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds removeFiles, onRemoveFiles #942

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 30 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 32 additions & 2 deletions src/index.js
Expand Up @@ -202,7 +202,14 @@ Dropzone.propTypes = {
* @param {object[]} files
* @param {(DragEvent|Event)} event
*/
onDropRejected: PropTypes.func
onDropRejected: PropTypes.func,

/**
* Cb for when a user removes one or more files.
*
* @param {object[]} files
*/
onRemoveFiles: PropTypes.func
}

export default Dropzone
Expand Down Expand Up @@ -269,6 +276,7 @@ export default Dropzone
* @property {File[]} draggedFiles Files in active drag
* @property {File[]} acceptedFiles Accepted files
* @property {File[]} rejectedFiles Rejected files
* @property {Function} removeFiles Accepts an array of File objects to be removed
*/

const initialState = {
Expand Down Expand Up @@ -368,6 +376,7 @@ export function useDropzone({
onDropAccepted,
onDropRejected,
onFileDialogCancel,
onRemoveFiles,
preventDropOnDocument = true,
noClick = false,
noKeyboard = false,
Expand Down Expand Up @@ -710,6 +719,26 @@ export function useDropzone({
[inputRef, accept, multiple, onDropCb, disabled]
)

const removeFiles = React.useCallback(
files => {
const acceptedFiles = state.acceptedFiles.filter(acceptedFile => {
return !files.find(f => f === acceptedFile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.prototype.find is not supported in IE: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

This project doesn't have an official list of supported browsers. See #630. That being said, the project isn't using find anywhere else, so this would be a breaking change for any project that isn't polyfilling find and that has IE users.

I don't maintain this project but I think it would be better to use Array.prototype.some instead. :)

});
const rejectedFiles = state.rejectedFiles.filter(rejectedFile => {
return !files.find(f => f === rejectedFile);
});

dispatch({
acceptedFiles,
rejectedFiles,
type: 'setFiles'
})

onRemoveFiles && onRemoveFiles(files)
},
[state, onRemoveFiles]
)

const fileCount = draggedFiles.length
const isMultipleAllowed = multiple || fileCount <= 1
const isDragAccept = fileCount > 0 && allFilesAccepted(draggedFiles, accept, minSize, maxSize)
Expand All @@ -724,7 +753,8 @@ export function useDropzone({
getInputProps,
rootRef,
inputRef,
open: composeHandler(openFileDialog)
open: composeHandler(openFileDialog),
removeFiles
}
}

Expand Down
77 changes: 77 additions & 0 deletions src/index.spec.js
Expand Up @@ -1914,6 +1914,83 @@ describe('useDropzone() hook', () => {
expect(onDropSpy).toHaveBeenCalledWith(files, [], expect.anything())
})

test('removeFiles removes file from files arrays', async () => {
const FileList = (props = { files: [] }) => (
<ul>
{props.files.map(file => (
<li key={file.name} data-type={props.type}>
{file.name}
</li>
))}
</ul>
)

const getAcceptedFiles = node => node.querySelectorAll(`[data-type="accepted"]`)
const getRejectedFiles = node => node.querySelectorAll(`[data-type="rejected"]`)

const ui = (
<Dropzone accept="image/*">
{({ getRootProps, getInputProps, acceptedFiles, rejectedFiles, removeFiles }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<button onClick={() => {
removeFiles([acceptedFiles[0], rejectedFiles[0]]);
}}>Remove</button>
<FileList files={acceptedFiles} type="accepted" />
<FileList files={rejectedFiles} type="rejected" />
</div>
)}
</Dropzone>
)
const { container } = render(ui)
const dropzone = container.querySelector('div')
const input = container.querySelector('input')

Object.defineProperty(input, 'files', { value: [...files, ...images] })

dispatchEvt(input, 'change')
await flushPromises(ui, container)

const button = container.querySelector('button')
dispatchEvt(button, 'click')

await flushPromises(ui, container)

const acceptedFileList = getAcceptedFiles(dropzone)
expect(acceptedFileList).toHaveLength(1)
const rejectedFileList = getRejectedFiles(dropzone)
expect(rejectedFileList).toHaveLength(0)
})

test('onRemoveFiles callback is invoked when removeFiles is called', async () => {
const onRemoveSpy = jest.fn()

const ui = (
<Dropzone onRemoveFiles={onRemoveSpy}>
{({ getRootProps, getInputProps, acceptedFiles, removeFiles }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
<button onClick={() => {
removeFiles([acceptedFiles[0]]);
}}>Remove</button>
</div>
)}
</Dropzone>
)
const { container } = render(ui)
const input = container.querySelector('input')

Object.defineProperty(input, 'files', { value: files })

dispatchEvt(input, 'change')
await flushPromises(ui, container)

const button = container.querySelector('button')
dispatchEvt(button, 'click')

expect(onRemoveSpy).toHaveBeenCalledWith(files)
})

it('sets {acceptedFiles, rejectedFiles}', async () => {
const FileList = (props = { files: [] }) => (
<ul>
Expand Down
2 changes: 2 additions & 0 deletions typings/react-dropzone.d.ts
Expand Up @@ -23,6 +23,7 @@ export type DropzoneOptions = Pick<React.HTMLProps<HTMLElement>, PropTypes> & {
onDropRejected?<T extends File>(files: T[], event: DropEvent): void;
getFilesFromEvent?(event: DropEvent): Promise<Array<File | DataTransferItem>>;
onFileDialogCancel?(): void;
onRemoveFiles?(files: File[]): void;
};

export type DropEvent = React.DragEvent<HTMLElement> | React.ChangeEvent<HTMLInputElement> | DragEvent | Event;
Expand All @@ -40,6 +41,7 @@ export type DropzoneState = DropzoneRef & {
inputRef: React.RefObject<HTMLInputElement>;
getRootProps(props?: DropzoneRootProps): DropzoneRootProps;
getInputProps(props?: DropzoneInputProps): DropzoneInputProps;
removeFiles(files: File[]): void;
};

export interface DropzoneRef {
Expand Down
1 change: 1 addition & 0 deletions typings/tests/all.tsx
Expand Up @@ -14,6 +14,7 @@ export default class Test extends React.Component {
onDropAccepted={(files, event) => console.log(files, event)}
onDropRejected={(files, event) => console.log(files, event)}
onFileDialogCancel={() => console.log("abc")}
onRemoveFiles={file => {}}
minSize={2000}
maxSize={Infinity}
preventDropOnDocument
Expand Down