I am facing the difficulty that I want to rewrite the component to use react hooks, yet suddenly the DropZone component stopped working. I need to change the Component to hooks due to the JWT authentication I use.
I have this (former) implementation of the component:
import React from "react";
import Dropzone from "react-dropzone";
import UploadFileService from "../../../services/upload-files.service";
import './FileUploadForm.css'
import {withTranslation} from "react-i18next";
import Button from "@mui/material/Button";
import LinearProgressWithLabel from "./LinearProgressWithLabel";
import Typography from "@mui/material/Typography";
import {Alert} from "@mui/material";
import AuthContext from "../../../context/AuthContext";
class FileUploadForm extends React.Component {
constructor(props) {
super(props);
this.upload = this.upload.bind(this);
this.removeFiles = this.removeFiles.bind(this);
this.onDrop = this.onDrop.bind(this);
this.uploadFiles = this.uploadFiles.bind(this);
this.state = {
selectedFiles: undefined,
progressInfos: [],
message: [],
fileInfos: [],
dropZoneVisible: true,
url: props.url,
multipleFiles: props.multipleFiles
};
}
onDrop(files) {
if (files.length > 0) {
this.setState({selectedFiles: files});
}
}
uploadFiles() {
const selectedFiles = this.state.selectedFiles;
let _progressInfos = [];
for (let i = 0; i < selectedFiles.length; i++) {
_progressInfos.push({percentage: 0, fileName: selectedFiles[i].name});
}
this.setState(
{
progressInfos: _progressInfos,
message: [],
},
() => {
for (let i = 0; i < selectedFiles.length; i++) {
this.upload(i, selectedFiles[i]);
}
}
);
}
upload(idx, file) {
let _progressInfos = [...this.state.progressInfos];
UploadFileService.upload(file, this.state.url, (event) => {
_progressInfos[idx].percentage = Math.round(
(100 * event.loaded) / event.total
);
this.setState({
_progressInfos,
});
})
.then((response) => {
this.setState((prev) => {
let nextMessage = [
...prev.message,
"Uploaded the file successfully: " + file.name,
];
return {
message: nextMessage,
dropZoneVisible: false
};
});
return UploadFileService.getFiles(this.state.url);
})
.then((files) => {
this.setState({
fileInfos: files.data,
});
})
.catch(() => {
_progressInfos[idx].percentage = 0;
this.setState((prev) => {
let nextMessage = [
...prev.message,
"Could not upload the file: " + file.name,
];
return {
progressInfos: _progressInfos,
message: nextMessage,
};
});
});
}
render() {
const {t} = this.props;
const {selectedFiles, progressInfos, message, fileInfos} = this.state;
return (
<div style={{marginTop: '1em'}}>
{progressInfos &&
progressInfos.map((progressInfo, index) => (
<div className="mb-2" key={index} style={{
textAlign: "center",
fontWeight: "bold"
}}>
<Typography component="span" variant="string" align="center">
{progressInfo.fileName}
</Typography>
<LinearProgressWithLabel variant="determinate" value={progressInfo.percentage}/>
</div>
))}
<Dropzone onDrop={this.onDrop} multiple={this.state.multipleFiles}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps({className: "dropzone"})}>
<input {...getInputProps()} />
{selectedFiles &&
Array.isArray(selectedFiles) &&
selectedFiles.length ? (
<div className="selected-file">
{selectedFiles.length > 3
? `${selectedFiles.length} files`
: selectedFiles.map((file) => file.name).join(", ")}
</div>
) : (
t("label", "Drag and drop file here, or click to select file", {ns: "fileUploadForm"})
)}
</div>
<aside className="selected-file-wrapper"
style={{
display: "flex",
justifyContent: "space-evenly"
}}
>
<Button
className="btn btn-success"
disabled={!selectedFiles}
variant="contained"
onClick={this.uploadFiles}
>
{t("button", "Submit", {ns: "fileUploadForm"})}
</Button>
<Button
className="btn"
color="error"
disabled={!selectedFiles}
variant="contained"
onClick={this.removeFiles}
>
{/*{t("button", "Submit", {ns: "fileUploadForm"})}*/}
Beheben
</Button>
</aside>
</section>
)}
</Dropzone>
{message.length > 0 && (
<div style={{
marginTop: "1em"
}}>
{message.map((item, i) => {
return (
<Alert severity={
item.includes("success") ? "success" : "warning"}>
{item}
</Alert>);
})}
</div>
)}
{fileInfos.length > 0 && (
<div className="card">
<div className="card-header">List of Files</div>
<ul className="list-group list-group-flush">
{fileInfos && fileInfos.map((file, index) => (
<li className="list-group-item" key={index}>
<a href={file.url}>{file.name}</a>
</li>
))}
</ul>
</div>
)}
</div>
);
}
}
export default withTranslation()(FileUploadForm);
This component works perfectly, meaning that the selectedFiles get propagated so the users can see what they submit. However, when changed to the functional implementation using hooks:
import React, {useCallback, useEffect, useRef, useState} from "react";
import Dropzone from "react-dropzone";
import './FileUploadForm.css'
import {useTranslation, withTranslation} from "react-i18next";
import Button from "@mui/material/Button";
import LinearProgressWithLabel from "./LinearProgressWithLabel";
import Typography from "@mui/material/Typography";
import {Alert} from "@mui/material";
import useAxios from "../../../utils/useAxios";
function FileUploadForm(url, multipleFiles, handleUploadReload) {
const api = useAxios();
const {t, i18n} = useTranslation()
const {selectedFiles, setSelectedFiles} = useState(undefined);
const {progressInfos, setProgressInfos} = useState({val: []});
const {message, setMessage} = useState([]);
const {targetUrl, setTargetUrl} = useState(url);
const {multipleFilesEnabled, setMultipleFilesEnabled} = useState(multipleFiles);
const {formUploaded, setFormUploaded} = useState(false);
const progressInfosRef = useRef(null)
const onDrop = (files) => {
if (files && files.length > 0) {
setSelectedFiles(files);
setProgressInfos({ val: [] });
}
}
const resetFormState = () => {
setFormUploaded(false);
setSelectedFiles(undefined);
setProgressInfos([]);
setMessage([]);
}
const removeFiles = () => {
setSelectedFiles(undefined);
setProgressInfos([]);
setMessage([]);
}
const uploadFiles = () => {
const files = Array.from(selectedFiles);
let _progressInfos = files.map(file => ({percentage: 0, fileName: file.name}));
progressInfosRef.current = {
val: _progressInfos,
}
files.map((file, i) => upload(i, file));
setMessage([]);
};
const upload = (idx, file) => {
let _progressInfos = [...progressInfosRef.current.val];
let formData = new FormData();
formData.append("file", file)
const onUploadProgress = (event) => {
_progressInfos[idx].percentage = Math.round(
(100 * event.loaded) / event.total
);
setProgressInfos({val: _progressInfos});
}
return api.post(targetUrl, formData, {
headers: {
"Content-Type": "multipart/form-data",
}, onUploadProgress
}).then((response) => {
handleUploadReload(response.data["files"]);
setMessage((prev) => ([
...prev.message,
"Uploaded the file successfully: " + file.name,
]
));
setFormUploaded(true);
})
.catch(() => {
_progressInfos[idx].percentage = 0;
setProgressInfos({val: _progressInfos});
setMessage((prev) => ([
...prev.message,
"Could not upload the file: " + file.name,
]));
});
}
return (
<div style={{marginTop: '1em'}}>
{progressInfos &&
progressInfos.val.length > 0 &&
progressInfos.val.map((progressInfo, index) => (
<div className="mb-2" key={index} style={{
textAlign: "center",
fontWeight: "bold"
}}>
<Typography component="span" variant="string" align="center">
{progressInfo.fileName}
</Typography>
<LinearProgressWithLabel variant="determinate" value={progressInfo.percentage}/>
</div>
))}
<Dropzone onDrop={onDrop} multiple={multipleFilesEnabled}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps({className: "dropzone"})}>
<input {...getInputProps()} />
{selectedFiles &&
Array.isArray(selectedFiles) &&
selectedFiles.length ? (
<div className="selected-file">
{selectedFiles.length > 3
? `${selectedFiles.length} files`
: selectedFiles.map((file) => file.name).join(", ")}
</div>
) : (
t("label", "Drag and drop file here, or click to select file", {ns: "fileUploadForm"})
)}
</div>
<aside className="selected-file-wrapper"
style={{
display: "flex",
justifyContent: "space-evenly"
}}
>
{!formUploaded ? [
<Button
className="btn btn-success"
disabled={!selectedFiles}
variant="contained"
onClick={uploadFiles}
>
{t("button", "Submit", {ns: "fileUploadForm"})}
</Button>,
<Button
className="btn"
color="error"
disabled={!selectedFiles}
variant="contained"
onClick={removeFiles}
>
{/* TODO: {t("button", "Submit", {ns: "fileUploadForm"})}*/}
Beheben
</Button>] : (
<Button
className="btn"
color="error"
variant="contained"
onClick={resetFormState}
>
{/*TODO: {t("button", "Submit", {ns: "fileUploadForm"})}*/}
Neu Hochladen
</Button>
)}
</aside>
</section>
)}
</Dropzone>
{message && message.length > 0 && (
<div style={{
marginTop: "1em"
}}>
{message.map((item, i) => {
return (
<Alert severity={
item.includes("success") ? "success" : "warning"}>
{item}
</Alert>);
})}
</div>
)}
</div>
);
}
export default withTranslation()(FileUploadForm);
where the utilization of useAxios is essential. Nevertheless, users cannot see what they uploaded (preload view before push request). Any clue what could be wrong?
TLDR
DropZone shows uploaded files in the browser (before post request) when FileUploadForm is class and does not in case of a function.