Skip to content

Commit f067ac8

Browse files
authored
V2 (#73)
* Fix useFilePicker return type for readAs ArrayBuffer * Bump version to 1.7.1 * rework hook props & validators, add onClear handler * restructure props and extend validator interface * rework errors * migrate to exports package.json property & add support for /validators and /types entrypoints * allow validator event handlers to return promises * Add generics for custom errors which allow for discriminated union over user defined errors * fix name for onFilesSuccessfullySelected handler * fix name of a variable in storybook * v2.0.0 * v2.0.0-canary.1 * v2.0.0-canary-1 * 2.0.0-canary2: fix subpackage exports * 2.0.0-canary3: add source mappings * 2.0.0-canary-4: fix build script * 2.0.0-canary-5: fix prepare to build script * Update README and version to 2.0.0 * Update twitter link in README
1 parent 8405f28 commit f067ac8

37 files changed

Lines changed: 3505 additions & 3924 deletions

README.md

Lines changed: 161 additions & 225 deletions
Large diffs are not rendered by default.

example/imperative.tsx

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,39 @@
11
import 'react-app-polyfill/ie11';
22
import * as React from 'react';
33
import { useImperativeFilePicker } from '../src';
4+
import { PersistentFileAmountLimitValidator } from '../src/validators';
45

56
const Imperative = () => {
6-
const [
7-
openFileSelector,
8-
{ filesContent, loading, errors, plainFiles, clear, removeFileByIndex, removeFileByReference },
9-
] = useImperativeFilePicker({
10-
multiple: true,
11-
readAs: 'Text',
12-
readFilesContent: true,
13-
onFilesSelected: ({ plainFiles, filesContent, errors }) => {
14-
// this callback is always called, even if there are errors
15-
console.log('onFilesSelected', plainFiles, filesContent, errors);
16-
},
17-
onFilesRejected: ({ errors }) => {
18-
// this callback is called when there were validation errors
19-
console.log('onFilesRejected', errors);
20-
},
21-
onFilesSuccessfulySelected: ({ plainFiles, filesContent }) => {
22-
// this callback is called when there were no validation errors
23-
console.log('onFilesSuccessfulySelected', plainFiles, filesContent);
24-
},
25-
});
7+
// for imperative file picker, if you want to limit amount of files selected by user, you need to pass persistent validator
8+
const validators = React.useMemo(() => [new PersistentFileAmountLimitValidator({ min: 2, max: 3 })], []);
269

27-
if (errors.length) {
28-
return (
29-
<div>
30-
<button onClick={() => openFileSelector()}>Something went wrong, retry! </button>
31-
<div style={{ display: 'flex', flexDirection: 'column' }}>
32-
{console.log(errors)}
33-
{Object.entries(errors[0])
34-
.filter(([key, value]) => key !== 'name' && value)
35-
.map(([key]) => (
36-
<div key={key}>{key}</div>
37-
))}
38-
</div>
39-
</div>
40-
);
41-
}
10+
const { openFilePicker, filesContent, loading, errors, plainFiles, clear, removeFileByIndex, removeFileByReference } =
11+
useImperativeFilePicker({
12+
multiple: true,
13+
readAs: 'Text',
14+
readFilesContent: true,
15+
validators,
16+
onFilesSelected: ({ plainFiles, filesContent, errors }) => {
17+
// this callback is always called, even if there are errors
18+
console.log('onFilesSelected', plainFiles, filesContent, errors);
19+
},
20+
onFilesRejected: ({ errors }) => {
21+
// this callback is called when there were validation errors
22+
console.log('onFilesRejected', errors);
23+
},
24+
onFilesSuccessfullySelected: ({ plainFiles, filesContent }) => {
25+
// this callback is called when there were no validation errors
26+
console.log('onFilesSuccessfullySelected', plainFiles, filesContent);
27+
},
28+
});
4229

4330
if (loading) {
4431
return <div>Loading...</div>;
4532
}
4633

4734
return (
4835
<div>
49-
<button onClick={async () => openFileSelector()}>Select file</button>
36+
<button onClick={async () => openFilePicker()}>Select file</button>
5037
<button onClick={() => clear()}>Clear</button>
5138
<br />
5239
Amount of selected files:
@@ -55,6 +42,22 @@ const Imperative = () => {
5542
Amount of filesContent:
5643
{filesContent.length}
5744
<br />
45+
{!!errors.length && (
46+
<>
47+
Errors:
48+
{errors.map((error, index) => (
49+
<div key={error.name}>
50+
<span>{index + 1}.</span>
51+
{Object.entries(error).map(([key, value]) => (
52+
<div key={key}>
53+
{key}: {typeof value === 'string' ? value : Array.isArray(value) ? value.join(', ') : null}
54+
</div>
55+
))}
56+
</div>
57+
))}
58+
</>
59+
)}
60+
<br />
5861
{plainFiles.map((file, i) => (
5962
<div>
6063
<div style={{ display: 'flex', alignItems: 'center' }} key={file.name}>

example/index.tsx

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,48 @@ import 'react-app-polyfill/ie11';
22
import * as React from 'react';
33
import * as ReactDOM from 'react-dom';
44
import { useFilePicker } from '../src';
5-
import type { Validator } from '../src';
5+
import { Validator, ImageDimensionsValidator, FileAmountLimitValidator } from '../src/validators';
6+
import { UseFilePickerConfig } from '../src/types';
67
import Imperative from './imperative';
8+
import { FileWithPath } from 'file-selector';
79

8-
const customValidator: Validator = {
10+
class CustomValidator extends Validator {
911
/**
1012
* Validation takes place before parsing. You have access to config passed as argument to useFilePicker hook
1113
* and all plain file objects of selected files by user. Called once for all files after selection.
1214
* Example validator below allowes only even amount of files
1315
* @returns {Promise} resolve means that file passed validation, reject means that file did not pass
1416
*/
15-
validateBeforeParsing: async (config, plainFiles) =>
16-
new Promise((res, rej) => (plainFiles.length % 2 === 0 ? res(undefined) : rej({ oddNumberOfFiles: true }))),
17+
async validateBeforeParsing(config: UseFilePickerConfig, plainFiles: File[]): Promise<void> {
18+
return new Promise((res, rej) => (plainFiles.length % 2 === 0 ? res(undefined) : rej({ oddNumberOfFiles: true })));
19+
}
1720
/**
1821
* Validation takes place after parsing (or is never called if readFilesContent is set to false).
1922
* You have access to config passed as argument to useFilePicker hook, FileWithPath object that is currently
2023
* being validated and the reader object that has loaded that file. Called individually for every file.
2124
* Example validator below allowes only if file hasn't been modified in last 24 hours
2225
* @returns {Promise} resolve means that file passed validation, reject means that file did not pass
2326
*/
24-
validateAfterParsing: async (config, file, reader) =>
25-
new Promise((res, rej) =>
27+
async validateAfterParsing(config: UseFilePickerConfig, file: FileWithPath, reader: FileReader): Promise<void> {
28+
return new Promise((res, rej) =>
2629
file.lastModified < new Date().getTime() - 24 * 60 * 60 * 1000
2730
? res(undefined)
2831
: rej({ fileRecentlyModified: true, lastModified: file.lastModified })
29-
),
30-
};
32+
);
33+
}
34+
}
3135

3236
const App = () => {
33-
const [openFileSelector, { filesContent, loading, errors, plainFiles, clear }] = useFilePicker({
37+
const { openFilePicker, filesContent, loading, errors, plainFiles, clear } = useFilePicker({
3438
multiple: true,
3539
readAs: 'DataURL', // availible formats: "Text" | "BinaryString" | "ArrayBuffer" | "DataURL"
36-
// accept: '.ics,.pdf',
3740
accept: ['.png', '.jpeg', '.heic'],
38-
limitFilesConfig: { min: 2, max: 3 },
39-
// minFileSize: 1, // in megabytes
40-
// maxFileSize: 1,
41-
// imageSizeRestrictions: {
42-
// maxHeight: 1024, // in pixels
43-
// maxWidth: 1024,
44-
// minHeight: 768,
45-
// minWidth: 768,
46-
// },
4741
// readFilesContent: false, // ignores file content,
48-
// validators: [customValidator],
42+
validators: [
43+
new FileAmountLimitValidator({ min: 1, max: 3 }),
44+
// new FileSizeValidator({ maxFileSize: 100_000 /* 100kb in bytes */ }),
45+
new ImageDimensionsValidator({ maxHeight: 600 }),
46+
],
4947
onFilesSelected: ({ plainFiles, filesContent, errors }) => {
5048
// this callback is always called, even if there are errors
5149
console.log('onFilesSelected', plainFiles, filesContent, errors);
@@ -54,42 +52,42 @@ const App = () => {
5452
// this callback is called when there were validation errors
5553
console.log('onFilesRejected', errors);
5654
},
57-
onFilesSuccessfulySelected: ({ plainFiles, filesContent }) => {
55+
onFilesSuccessfullySelected: ({ plainFiles, filesContent }) => {
5856
// this callback is called when there were no validation errors
59-
console.log('onFilesSuccessfulySelected', plainFiles, filesContent);
57+
console.log('onFilesSuccessfullySelected', plainFiles, filesContent);
6058
},
6159
});
6260

63-
if (errors.length) {
64-
return (
65-
<div>
66-
<button onClick={() => openFileSelector()}>Something went wrong, retry! </button>
67-
<div style={{ display: 'flex', flexDirection: 'column' }}>
68-
{console.log(errors)}
69-
{Object.entries(errors[0])
70-
.filter(([key, value]) => key !== 'name' && value)
71-
.map(([key]) => (
72-
<div key={key}>{key}</div>
73-
))}
74-
</div>
75-
</div>
76-
);
77-
}
78-
7961
if (loading) {
8062
return <div>Loading...</div>;
8163
}
8264

8365
return (
8466
<div>
85-
<button onClick={async () => openFileSelector()}>Select file</button>
67+
<button onClick={async () => openFilePicker()}>Select file</button>
8668
<button onClick={() => clear()}>Clear</button>
8769
<br />
8870
Amount of selected files:
8971
{plainFiles.length}
9072
<br />
73+
{errors.length ? (
74+
<div style={{ display: 'flex', flexDirection: 'column' }}>
75+
<div style={{ marginTop: '10px' }}>Errors:</div>
76+
{errors.map((error, index) => (
77+
<div key={error.name}>
78+
<span>{index + 1}.</span>
79+
{Object.entries(error).map(([key, value]) => (
80+
<div key={key}>
81+
{key}: {typeof value === 'string' ? value : Array.isArray(value) ? value.join(', ') : null}
82+
</div>
83+
))}
84+
</div>
85+
))}
86+
</div>
87+
) : null}
9188
{/* If readAs is set to DataURL, You can display an image */}
92-
{!!filesContent.length && <img src={filesContent[0].content} />}
89+
{!!filesContent.length ? <img src={filesContent[0].content} /> : null}
90+
{!!filesContent.length ? <div>{filesContent[0].content}</div> : null}
9391
<br />
9492
{plainFiles.map(file => (
9593
<div key={file.name}>{file.name}</div>

0 commit comments

Comments
 (0)