diff --git a/examples/validator/README.md b/examples/validator/README.md
new file mode 100644
index 00000000..82845a0e
--- /dev/null
+++ b/examples/validator/README.md
@@ -0,0 +1,68 @@
+By providing `validator` prop you can specify custom validation for files.
+
+The value must be a function that accepts File object and returns null if file should be accepted or error object/array of error objects if file should me rejected.
+
+```jsx harmony
+import React from 'react';
+import {useDropzone} from 'react-dropzone';
+
+const maxLength = 20;
+
+function nameLengthValidator(file) {
+ if (file.name.length > maxLength) {
+ return {
+ code: "name-too-large",
+ message: `Name is larger than ${maxLength} characters`
+ };
+ }
+
+ return null
+}
+
+function CustomValidation(props) {
+ const {
+ acceptedFiles,
+ fileRejections,
+ getRootProps,
+ getInputProps
+ } = useDropzone({
+ validator: nameLengthValidator
+ });
+
+ const acceptedFileItems = acceptedFiles.map(file => (
+
+ {file.path} - {file.size} bytes
+
+ ));
+
+ const fileRejectionItems = fileRejections.map(({ file, errors }) => (
+
+ {file.path} - {file.size} bytes
+
+ {errors.map(e => (
+ - {e.message}
+ ))}
+
+
+ ));
+
+ return (
+
+
+
+
Drag 'n' drop some files here, or click to select files
+
(Only files with name less than 20 characters will be accepted)
+
+
+
+ );
+}
+
+
+```
+
diff --git a/src/index.js b/src/index.js
index 9d8ee83e..f6308703 100755
--- a/src/index.js
+++ b/src/index.js
@@ -60,7 +60,8 @@ const defaultProps = {
noClick: false,
noKeyboard: false,
noDrag: false,
- noDragEventsBubbling: false
+ noDragEventsBubbling: false,
+ validator: null
}
Dropzone.defaultProps = defaultProps
@@ -226,7 +227,14 @@ Dropzone.propTypes = {
* @param {FileRejection[]} fileRejections
* @param {(DragEvent|Event)} event
*/
- onDropRejected: PropTypes.func
+ onDropRejected: PropTypes.func,
+
+ /**
+ * Custom validation function
+ * @param {File} file
+ * @returns {FileError|FileError[]}
+ */
+ validator: PropTypes.func
}
export default Dropzone
@@ -398,7 +406,8 @@ export function useDropzone(options = {}) {
noClick,
noKeyboard,
noDrag,
- noDragEventsBubbling
+ noDragEventsBubbling,
+ validator
} = {
...defaultProps,
...options
@@ -615,11 +624,18 @@ export function useDropzone(options = {}) {
files.forEach(file => {
const [accepted, acceptError] = fileAccepted(file, accept)
const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize)
- if (accepted && sizeMatch) {
+ const customErrors = validator ? validator(file) : null;
+
+ if (accepted && sizeMatch && !customErrors) {
acceptedFiles.push(file)
} else {
- const errors = [acceptError, sizeError].filter(e => e)
- fileRejections.push({ file, errors })
+ let errors = [acceptError, sizeError];
+
+ if (customErrors) {
+ errors = errors.concat(customErrors);
+ }
+
+ fileRejections.push({ file, errors: errors.filter(e => e) })
}
})
diff --git a/src/index.spec.js b/src/index.spec.js
index bff9bc74..74e7ded4 100644
--- a/src/index.spec.js
+++ b/src/index.spec.js
@@ -2719,6 +2719,47 @@ describe('useDropzone() hook', () => {
expect(fn).not.toThrow()
})
})
+
+ describe('validator', () => {
+ it('rejects with custom error', async () => {
+ const validator = file => {
+ if (/dogs/i.test(file.name))
+ return { code: 'dogs-not-allowed', message: 'Dogs not allowed' };
+
+ return null;
+ }
+
+ const onDropSpy = jest.fn()
+
+ const ui = (
+
+ {({ getRootProps, getInputProps }) => (
+
+
+
+ )}
+
+ )
+
+ const { container, rerender } = render(ui)
+ const dropzone = container.querySelector('div')
+
+ fireDrop(dropzone, createDtWithFiles(images))
+ await flushPromises(rerender, ui)
+
+ expect(onDropSpy).toHaveBeenCalledWith([images[0]], [
+ {
+ file: images[1],
+ errors: [
+ {
+ code: 'dogs-not-allowed',
+ message: 'Dogs not allowed',
+ }
+ ]
+ }
+ ], expect.anything())
+ })
+ })
})
async function flushPromises(rerender, ui) {
diff --git a/styleguide.config.js b/styleguide.config.js
index 96d54a4e..0f37ef2d 100644
--- a/styleguide.config.js
+++ b/styleguide.config.js
@@ -59,6 +59,10 @@ module.exports = {
name: 'Accepting specific number of files',
content: 'examples/maxFiles/README.md'
},
+ {
+ name: 'Custom validation',
+ content: 'examples/validator/README.md'
+ },
{
name: 'Opening File Dialog Programmatically',
content: 'examples/file-dialog/README.md'
diff --git a/typings/react-dropzone.d.ts b/typings/react-dropzone.d.ts
index f038ce7a..4284f353 100644
--- a/typings/react-dropzone.d.ts
+++ b/typings/react-dropzone.d.ts
@@ -10,7 +10,7 @@ export interface DropzoneProps extends DropzoneOptions {
export interface FileError {
message: string;
- code: "file-too-large" | "file-too-small"|"too-many-files"|"file-invalid-type";
+ code: "file-too-large" | "file-too-small" | "too-many-files" | "file-invalid-type" | string;
}
export interface FileRejection {
@@ -34,6 +34,7 @@ export type DropzoneOptions = Pick, PropTypes> & {
onDropRejected?: (fileRejections: FileRejection[], event: DropEvent) => void;
getFilesFromEvent?: (event: DropEvent) => Promise>;
onFileDialogCancel?: () => void;
+ validator?: (file: T) => FileError | FileError[];
};
export type DropEvent = React.DragEvent | React.ChangeEvent | DragEvent | Event;