Skip to content

Commit 37306d9

Browse files
committed
feat: make <Dropzone> headless
Also closes #161, closes #468, closes #640, closes #706 and closes #718. BREAKING CHANGE: The `<Dropzone>` component now requires a rendering function, either provided as the children or as a prop. Before: ``` <Dropzone /> ``` After: ``` <Dropzone> {({getRootProps}) => <div {...getRootProps()} />} </Dropzone> ```
1 parent 9968ca8 commit 37306d9

File tree

29 files changed

+3746
-2199
lines changed

29 files changed

+3746
-2199
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ styleguide
1414

1515
.DS_Store
1616

17+
.envrc
18+
1719
coverage
1820
Changelog.md

README.md

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,36 +33,87 @@ yarn add react-dropzone
3333

3434
## Usage
3535

36-
Import `Dropzone` in your React component:
37-
3836
```javascript static
37+
import React from 'react'
38+
import classNames from 'classnames'
3939
import Dropzone from 'react-dropzone'
40+
41+
class MyDropzone extends React.Component {
42+
onDrop = (acceptedFiles, rejectedFiles) => {
43+
// Do something with files
44+
}
45+
46+
render() {
47+
return (
48+
<Dropzone onDrop={this.onDrop}>
49+
{({getRootProps, getInputProps, isDragActive}) => {
50+
return (
51+
<div
52+
{...getRootProps()}
53+
className={classNames('dropzone', {'dropzone--isActive': isDragActive})}
54+
>
55+
<input {...getInputProps()} />
56+
{
57+
isDragActive ?
58+
<p>Drop files here...</p> :
59+
<p>Try dropping some files here, or click to select files to upload.</p>
60+
}
61+
</div>
62+
)
63+
}}
64+
</Dropzone>
65+
);
66+
}
67+
}
4068
```
4169

42-
and specify the `onDrop` method that accepts two arguments. The first argument represents the accepted files and the second argument the rejected files.
70+
## Render Prop Function
4371

44-
```javascript static
45-
function onDrop(acceptedFiles, rejectedFiles) {
46-
// do stuff with files...
47-
}
72+
The render property function is what you use to render whatever you want to based on the state of `Dropzone`:
73+
```jsx static
74+
<Dropzone>
75+
{({getRootProps}) => <div {...getRootProps()} />}
76+
</Dropzone>
4877
```
4978

50-
Files accepted or rejected based on `accept` prop. This must be a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file).
79+
### Prop Getters
80+
See https://react-dropzone.netlify.com/#proptypes `{children}` for more info.
81+
82+
These functions are used to apply props to the elements that you render.
5183

52-
Please note that `onDrop` method will always be called regardless if dropped file was accepted or rejected. The `onDropAccepted` method will be called if all dropped files were accepted and the `onDropRejected` method will be called if any of the dropped files was rejected.
84+
This gives you maximum flexibility to render what, when, and wherever you like. You call these on the element in question (for example: `<div {...getRootProps()} />`).
5385

54-
Using `react-dropzone` is similar to using a file form field, but instead of getting the `files` property from the field, you listen to the `onDrop` callback to handle the files. Simple explanation here: http://abandon.ie/notebook/simple-file-uploads-using-jquery-ajax
86+
You should pass all your props to that function rather than applying them on the element yourself to avoid your props being overridden (or overriding the props returned).
87+
E.g.
88+
```jsx static
89+
<div
90+
{...getRootProps({
91+
onClick: evt => console.log(event)
92+
})}
93+
/>
94+
```
5595

56-
Specifying the `onDrop` method, provides you with an array of [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) which you can then send to a server. For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
96+
### State
97+
See https://react-dropzone.netlify.com/#proptypes `{children}` for more info.
98+
99+
### Custom refKey
100+
101+
Both `getRootProps` and `getInputProps` accept custom `refKey` (defaulted to `ref`) as one of the attributes passed down in the parameter.
57102

58103
```javascript static
59-
onDrop: acceptedFiles => {
60-
const req = request.post('/upload');
61-
acceptedFiles.forEach(file => {
62-
req.attach(file.name, file);
63-
});
64-
req.end(callback);
65-
}
104+
const StyledDropArea = styled.div`
105+
// Some styling here
106+
`
107+
const Example = () => (
108+
<Dropzone>
109+
{({ getRootProps, getInputProps }) => (
110+
<StyledDropArea {...getRootProps({ refKey: 'innerRef' })}>
111+
<input {...getInputProps()} />
112+
<p>Drop some files here</p>
113+
</StyledDropArea>
114+
)}
115+
</Dropzone>
116+
);
66117
```
67118

68119
**Warning**: On most recent browsers versions, the files given by `onDrop` won't have properties `path` or `fullPath`, see [this SO question](https://stackoverflow.com/a/23005925/2275818) and [this issue](https://github.com/react-dropzone/react-dropzone/issues/477).

examples/Accept/Readme.md

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,23 @@ class Accept extends React.Component {
2727
accept="image/jpeg, image/png"
2828
onDrop={(accepted, rejected) => { this.setState({ accepted, rejected }); }}
2929
>
30-
<p>Try dropping some files here, or click to select files to upload.</p>
31-
<p>Only *.jpeg and *.png images will be accepted</p>
30+
{({ getRootProps, getInputProps }) => (
31+
<div {...getRootProps()} className="dropzone">
32+
<input {...getInputProps()} />
33+
<p>Try dropping some files here, or click to select files to upload.</p>
34+
<p>Only *.jpeg and *.png images will be accepted</p>
35+
</div>
36+
)}
3237
</Dropzone>
3338
</div>
3439
<aside>
35-
<h2>Accepted files</h2>
40+
<h4>Accepted files</h4>
3641
<ul>
3742
{
3843
this.state.accepted.map(f => <li key={f.name}>{f.name} - {f.size} bytes</li>)
3944
}
4045
</ul>
41-
<h2>Rejected files</h2>
46+
<h4>Rejected files</h4>
4247
<ul>
4348
{
4449
this.state.rejected.map(f => <li key={f.name}>{f.name} - {f.size} bytes</li>)
@@ -59,37 +64,35 @@ Because of HTML5 File API differences across different browsers during the drag,
5964

6065
Also, at this moment it's not possible to read file names (and thus, file extensions) during the drag operation. For that reason, if you want to react on different file types _during_ the drag operation, _you have to use_ mime types and not extensions! For example, the following example won't work even in Chrome:
6166

62-
```
63-
<Dropzone
64-
accept=".jpeg,.png"
65-
>
66-
{({ isDragActive, isDragReject }) => {
67-
if (isDragActive) {
68-
return "All files will be accepted";
69-
}
70-
if (isDragReject) {
71-
return "Some files will be rejected";
72-
}
73-
return "Dropping some files here...";
74-
}}
67+
```jsx harmony
68+
<Dropzone accept=".jpeg,.png">
69+
{({ getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject }) => (
70+
<div {...getRootProps()} className="dropzone">
71+
<input {...getInputProps()} />
72+
<div>
73+
{isDragAccept && "All files will be accepted"}
74+
{isDragReject && "Some files will be rejected"}
75+
{!isDragActive && "Drop some files here..."}
76+
</div>
77+
</div>
78+
)}
7579
</Dropzone>
7680
```
7781

7882
but this one will:
7983

80-
```
81-
<Dropzone
82-
accept="image/jpeg, image/png"
83-
>
84-
{({ isDragActive, isDragReject }) => {
85-
if (isDragActive) {
86-
return "All files will be accepted";
87-
}
88-
if (isDragReject) {
89-
return "Some files will be rejected";
90-
}
91-
return "Dropping some files here...";
92-
}}
84+
```jsx harmony
85+
<Dropzone accept="image/jpeg, image/png">
86+
{({ getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject }) => (
87+
<div {...getRootProps()} className="dropzone">
88+
<input {...getInputProps()} />
89+
<div>
90+
{isDragAccept && "All files will be accepted"}
91+
{isDragReject && "Some files will be rejected"}
92+
{!isDragActive && "Drop some files here..."}
93+
</div>
94+
</div>
95+
)}
9396
</Dropzone>
9497
```
9598

examples/Basic/Readme.md

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
Dropzone with default properties and displays list of the dropped files.
1+
By default, Dropzone just renders provided children without applying any styles.
2+
23

34
```jsx harmony
45
class Basic extends React.Component {
56
constructor() {
67
super()
7-
this.state = { files: [] }
8+
this.state = {
9+
files: []
10+
}
811
}
912

1013
onDrop(files) {
11-
this.setState({
12-
files
13-
});
14+
this.setState({files});
1415
}
1516

1617
onCancel() {
@@ -20,23 +21,28 @@ class Basic extends React.Component {
2021
}
2122

2223
render() {
24+
const files = this.state.files.map(file => (
25+
<li key={file.name}>
26+
{file.name} - {file.size} bytes
27+
</li>
28+
))
29+
2330
return (
2431
<section>
25-
<div className="dropzone">
26-
<Dropzone
27-
onDrop={this.onDrop.bind(this)}
28-
onFileDialogCancel={this.onCancel.bind(this)}
29-
>
30-
<p>Try dropping some files here, or click to select files to upload.</p>
31-
</Dropzone>
32-
</div>
32+
<Dropzone
33+
onDrop={this.onDrop.bind(this)}
34+
onFileDialogCancel={this.onCancel.bind(this)}
35+
>
36+
{({getRootProps, getInputProps}) => (
37+
<div {...getRootProps()}>
38+
<input {...getInputProps()} />
39+
<p>Drop files here, or click to select files</p>
40+
</div>
41+
)}
42+
</Dropzone>
3343
<aside>
34-
<h2>Dropped files</h2>
35-
<ul>
36-
{
37-
this.state.files.map(f => <li key={f.name}>{f.name} - {f.size} bytes</li>)
38-
}
39-
</ul>
44+
<h4>Files</h4>
45+
<ul>{files}</ul>
4046
</aside>
4147
</section>
4248
);
@@ -52,33 +58,55 @@ Dropzone with `disabled` property:
5258
class Basic extends React.Component {
5359
constructor() {
5460
super()
55-
this.state = { disabled: true, files: [] }
61+
this.state = {
62+
disabled: true,
63+
files: []
64+
}
5665
}
5766

5867
onDrop(files) {
68+
this.setState({files});
69+
}
70+
71+
toggleDisabled() {
5972
this.setState({
60-
files
61-
});
73+
disabled: !this.state.disabled
74+
})
6275
}
6376

6477
render() {
78+
const files = this.state.files.map(file => (
79+
<li key={file.name}>
80+
{file.name} - {file.size} bytes
81+
</li>
82+
))
83+
6584
return (
6685
<section>
6786
<aside>
68-
<button type="button" onClick={() => this.setState({ disabled: !this.state.disabled })}>Toggle disabled</button>
87+
<button
88+
type="button"
89+
onClick={this.toggleDisabled.bind(this)}
90+
>
91+
Toggle disabled
92+
</button>
6993
</aside>
7094
<div className="dropzone">
71-
<Dropzone onDrop={this.onDrop.bind(this)} disabled={this.state.disabled}>
72-
<p>Try dropping some files here, or click to select files to upload.</p>
95+
<Dropzone
96+
onDrop={this.onDrop.bind(this)}
97+
disabled={this.state.disabled}
98+
>
99+
{({getRootProps, getInputProps}) => (
100+
<div {...getRootProps()}>
101+
<input {...getInputProps()} />
102+
<p>Drop files here, or click to select files</p>
103+
</div>
104+
)}
73105
</Dropzone>
74106
</div>
75107
<aside>
76-
<h2>Dropped files</h2>
77-
<ul>
78-
{
79-
this.state.files.map(f => <li key={f.name}>{f.name} - {f.size} bytes</li>)
80-
}
81-
</ul>
108+
<h4>Files</h4>
109+
<ul>{files}</ul>
82110
</aside>
83111
</section>
84112
);

0 commit comments

Comments
 (0)