I have built a wrapper around react-dropzone:
Dropzone.jsx
import React, { useState, useCallback } from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import { useDropzone } from "react-dropzone";
import { noop } from "../utils";
const Dropzone = ({
id,
name,
label,
className,
touched,
error,
showError,
children,
onDrop,
showUploadedFiles,
onChange,
onRemoveFile // ...rest
}) => {
const [uploadedFiles, setUploadedFiles] = useState([]);
const _onChange = value => {
onChange();
console.log(">>>>>", "onchange triggerd", value);
setUploadedFiles(prevValue => [
...prevValue.map(fileObj => {
if (fileObj.file.name === value.filename) {
return {
id: value.id,
file: fileObj.file
};
}
return fileObj;
})
]);
};
const _onDrop = useCallback(
acceptedFiles => {
setUploadedFiles(prevValue => [
...prevValue,
...acceptedFiles.map(file => ({
id: "PLACEHOLDER",
file
}))
]);
onDrop(acceptedFiles);
},
[onDrop]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop: _onDrop
});
return (
// eslint-disable-next-line jsx-a11y/label-has-for
<label htmlFor={id || name}>
<span className="label-text">{label}</span>
{showUploadedFiles && (
<div className="uploaded-files">
{uploadedFiles.length > 0
? uploadedFiles.map(fileObj => (
<div className="uploaded-file">
<span className="file-name">{fileObj.file.name}</span>
</div>
))
: "No files uploaded"}
</div>
)}
<div
{...getRootProps()}
onChange={_onChange}
className="dropzone-container"
>
<input {...getInputProps()} />
<div className={classNames("dropzone", className)}>
{children ? (
children(isDragActive)
) : (
<div className="dropzone-content">
<span>Upload</span>
</div>
)}
</div>
</div>
{showError && touched && error.message && (
<span className="error">{error.message}</span>
)}
</label>
);
};
Dropzone.propTypes = {
id: PropTypes.string,
name: PropTypes.string.isRequired,
placeholder: PropTypes.string,
label: PropTypes.string,
control: PropTypes.instanceOf(Object).isRequired,
className: PropTypes.string,
popperClassName: PropTypes.string,
touched: PropTypes.bool,
error: PropTypes.instanceOf(Object),
showError: PropTypes.bool,
children: PropTypes.func,
onDrop: PropTypes.func,
showUploadedFiles: PropTypes.bool,
onChange: PropTypes.func,
onRemoveFile: PropTypes.func
};
Dropzone.defaultProps = {
id: "",
placeholder: "",
label: "",
className: "",
popperClassName: "",
touched: false,
error: {},
showError: false,
children: null,
onDrop: noop,
showUploadedFiles: false,
onChange: noop,
onRemoveFile: noop
};
export default Dropzone;
And a wrapper around Dropzone.jsx to use it with react-hook-form.
DropzoneWrapper.jsx
import React from "react";
import PropTypes from "prop-types";
import { Controller } from "react-hook-form";
import Dropzone from "./Dropzone";
const DropzoneWrapper = ({ control, ...rest }) => (
<Controller as={Dropzone} control={control} showError {...rest} />
);
DropzoneWrapper.propTypes = {
control: PropTypes.instanceOf(Object).isRequired
};
export default DropzoneWrapper;
I have also built a new component for Form. But I won't go into the details of that as it is not necessary to show it here for solving this problem. But I have built a codesandbox, in which we have a file called Form.jsx
In the App.jsx, When I click the Click Me button, I call setValue on Dropzone.
import React from "react";
import "./styles.css";
import { useForm } from "react-hook-form";
import Form from "./controls/Form";
import DropzoneWrapper from "./controls/DropzoneWrapper";
export default function App() {
const formMethods = useForm({});
const { setValue } = formMethods;
const onButtonClick = () => {
setValue("example", { filename: "test", id: "1234" });
};
return (
<div className="App">
<Form methods={formMethods}>
<DropzoneWrapper field name="example" showUploadedFiles />
<button onClick={onButtonClick} type="button">
Click Me
</button>
</Form>
</div>
);
}
I don't know how to wire it up with onChange function of Dropzone.jsx, so that when I do setValue('example', something), console.log inside _onChange of Dropzone.jsx is called.
Again here is the codesandbox link if you missed it:
Update
@Sabit Rakhim Thank you for your hard work and time. But unfortunately this is not what I intended. Button click is just an example here.
- Actually, when file is chosen, onDrop function will be called.
- I will fire a redux action in the body of onDrop function, which will call the API to upload the file.
- When file is uploaded, server will return me uuid.
- Now, I will fetch that uuid in my App.jsx file and then, I will send it to the dropzone component by doing setValue.
Now, if my component (dropzone in this case)'s onChange method gets called by setValue in App.jsx, then I will replace the id of uploaded file from PLACEHOLDER to actual id.
Now, when I submit the form, I should get those files inside the form's values that are being passed as the parameter to submit function.
This is the whole flow.