I am able to upload images using Uppy on my react app:
Upon selection and upload of an image, I see a preview of the image, along with options to remove it, add more, etc.:
After clicking the "Continue" button,
the state of reporting
(from const [reporting, setReporting] = useState(false);
) is set to true, which I think triggers a re-render of the DOM, including the disappearance of the UploadManager
component, which contains the Uppy dashboard pictured above. Now, instead, a <ReportForm>
component is rendered:
...
{reporting ? (
<ReportForm assetReferences={files} exifData={exifData} />
) : (
<>
<Grid item style={{ marginTop: 20 }}>
<UploadManager
onUploadStarted={() => setUploadInProgress(true)}
onUploadComplete={() => setUploadInProgress(false)}
...
When users click, "Back to Photographs" from the <ReportForm>
component (see image above), it simply resets the state of reporting
back to false. However, the Uppy Dashboard now shows its default, "Drop files here or browse files" again (see first image). I would like to see a preview of the images I just uploaded instead.
I am very new to React, but I can see that the uppyInstance is created in a useEffect hook, which seems to get called upon change of the reporting
state. I suspect that this is what is "resetting" the uppy Dashboard (see code for the uppy-related component below):
UploadManager.jsx:
import React, { useEffect, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
import { get } from 'lodash-es';
import Uppy from '@uppy/core';
import Tus from '@uppy/tus';
import Dashboard from '@uppy/react/lib/Dashboard';
import Skeleton from '@material-ui/lab/Skeleton';
import Cropper from './Cropper';
import '@uppy/core/dist/style.css';
import '@uppy/dashboard/dist/style.css';
const dashboardWidth = 600;
const dashboardHeight = 400;
export default function UploadManager({
files,
assetSubmissionId,
onUploadStarted = Function.prototype,
onUploadComplete = Function.prototype,
setFiles,
exifData,
disabled = false,
alreadyFiles = false,
}) {
const intl = useIntl();
const currentExifData = useRef();
currentExifData.current = exifData;
const [uppy, setUppy] = useState(null);
const [cropper, setCropper] = useState({
open: false,
imgSrc: null,
});
/* Resolves closure / useEffect issue */
// https://www.youtube.com/watch?v=eTDnfS2_WE4&feature=youtu.be
const fileRef = useRef([]);
fileRef.current = files;
if (alreadyFiles) {
// MAYBE I CAN DO SOMETHING HERE???
}
useEffect(() => {
console.log('deleteMe useEffect entered');
const uppyInstance = Uppy({
meta: { type: 'Report sightings image upload' },
restrictions: {
allowedFileTypes: ['.jpg', '.jpeg', '.png'],
},
autoProceed: true,
// browserBackButtonClose: true,
});
uppyInstance.use(Tus, {
endpoint: `${__houston_url__}/api/v1/asset_groups/tus`,
headers: {
'x-tus-transaction-id': assetSubmissionId,
},
});
uppyInstance.on('upload', onUploadStarted);
uppyInstance.on('complete', uppyState => {
const uploadObjects = get(uppyState, 'successful', []);
const assetReferences = uploadObjects.map(o => ({
path: o.name,
transactionId: assetSubmissionId,
}));
onUploadComplete();
// console.log('deleteMe fileRef.current is: ');
// console.log(fileRef.current);
// console.log('deleteMe ...fileRef.current is: ');
// console.log(...fileRef.current);
// // eslint-disable-next-line no-debugger
// debugger;
setFiles([...fileRef.current, ...assetReferences]);
});
uppyInstance.on('file-removed', (file, reason) => {
if (reason === 'removed-by-user') {
const newFiles = fileRef.current.filter(
f => f.path !== file.name,
);
setFiles(newFiles);
}
});
setUppy(uppyInstance);
return () => {
if (uppyInstance) uppyInstance.close();
};
}, []);
return (
<div
style={{
opacity: disabled ? 0.5 : 1,
pointerEvents: disabled ? 'none' : undefined,
}}
>
{cropper.open && (
<Cropper
imgSrc={cropper.imgSrc}
onClose={() => setCropper({ open: false, imgSrc: null })}
setCrop={croppedImage => {
const currentFile = files.find(
f => f.filePath === cropper.imgSrc,
);
const otherFiles = files.filter(
f => f.filePath !== cropper.imgSrc,
);
setFiles([
...otherFiles,
{ ...currentFile, croppedImage },
]);
}}
/>
)}
{uppy ? (
<div style={{ marginBottom: 32, maxWidth: dashboardWidth }}>
<Dashboard
uppy={uppy}
note={intl.formatMessage({ id: 'UPPY_IMAGE_NOTE' })}
showLinkToFileUploadResult={false}
showProgressDetails
showRemoveButtonAfterComplete
doneButtonHandler={null}
height={dashboardHeight}
locale={{
strings: {
dropHereOr: intl.formatMessage({
id: 'UPPY_DROP_IMAGES',
}),
browse: intl.formatMessage({ id: 'UPPY_BROWSE' }),
uploading: intl.formatMessage({
id: 'UPPY_UPLOADING',
}),
complete: intl.formatMessage({ id: 'UPPY_COMPLETE' }),
uploadFailed: intl.formatMessage({
id: 'UPPY_UPLOAD_FAILED',
}),
paused: intl.formatMessage({ id: 'UPPY_PAUSED' }),
retry: intl.formatMessage({ id: 'UPPY_RETRY' }),
cancel: intl.formatMessage({ id: 'UPPY_CANCEL' }),
filesUploadedOfTotal: {
0: intl.formatMessage({
id: 'UPPY_ONE_FILE_PROGRESS',
}),
1: intl.formatMessage({
id: 'UPPY_MULTIPLE_FILES_PROGRESS',
}),
},
dataUploadedOfTotal: intl.formatMessage({
id: 'UPPY_DATA_UPLOADED',
}),
xTimeLeft: intl.formatMessage({
id: 'UPPY_TIME_LEFT',
}),
uploadXFiles: {
0: intl.formatMessage({
id: 'UPPY_UPLOAD_ONE_FILE',
}),
1: intl.formatMessage({
id: 'UPPY_UPLOAD_MULTIPLE_FILES',
}),
},
uploadXNewFiles: {
0: intl.formatMessage({
id: 'UPPY_PLUS_UPLOAD_ONE_FILE',
}),
1: intl.formatMessage({
id: 'UPPY_PLUS_UPLOAD_MULTIPLE_FILES',
}),
},
},
}}
/>
</div>
) : (
<Skeleton
variant="rect"
style={{
width: '100%',
maxWidth: dashboardWidth,
height: dashboardHeight,
}}
/>
)}
</div>
);
}
Here is the code for the parent component in which UploadManager lives:
import React, { useState, useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import { v4 as uuid } from 'uuid';
import Grid from '@material-ui/core/Grid';
import InfoIcon from '@material-ui/icons/InfoOutlined';
import UploadManager from '../../components/report/UploadManager';
import ReportSightingsPage from '../../components/report/ReportSightingsPage';
import Text from '../../components/Text';
import Link from '../../components/Link';
import Button from '../../components/Button';
import ReportForm from './ReportForm';
// import useAssetFiles from '../../hooks/useAssetFiles';
export default function ReportSighting({ authenticated }) {
// console.log('deleteMe ReportSighting called');
const assetSubmissionId = useMemo(uuid, []);
const [uploadInProgress, setUploadInProgress] = useState(false);
const [alreadyFiles, setAlreadyFiles] = useState(false);
const [files, setFiles] = useState([]);
const [exifData, setExifData] = useState([]);
const [reporting, setReporting] = useState(false);
const noImages = files.length === 0;
// const {
// setFilesFromComponent,
// getFilesFromComponent,
// } = useAssetFiles();
const onBack = () => {
window.scrollTo(0, 0);
// setFilesFromComponent(files);
// setFiles(files);
if (files) setAlreadyFiles(true);
setReporting(false);
};
let continueButtonText = 'CONTINUE';
if (noImages) continueButtonText = 'CONTINUE_WITHOUT_PHOTOGRAPHS';
if (uploadInProgress) continueButtonText = 'UPLOAD_IN_PROGRESS';
return (
<ReportSightingsPage
titleId="REPORT_A_SIGHTING"
authenticated={authenticated}
>
{reporting ? (
<Button
onClick={onBack}
style={{ marginTop: 8, width: 'fit-content' }}
display="back"
id="BACK_TO_PHOTOS"
/>
) : null}
{reporting ? (
<ReportForm assetReferences={files} exifData={exifData} />
) : (
<>
<Grid item style={{ marginTop: 20 }}>
<UploadManager
onUploadStarted={() => setUploadInProgress(true)}
onUploadComplete={() => setUploadInProgress(false)}
assetSubmissionId={assetSubmissionId}
exifData={exifData}
setExifData={setExifData}
files={
files
// getFilesFromComponent()
// ? getFilesFromComponent()
// : files
}
setFiles={setFiles}
alreadyFiles={alreadyFiles}
/>
<div
style={{
display: 'flex',
alignItems: 'center',
marginTop: 20,
}}
>
<InfoIcon fontSize="small" style={{ marginRight: 4 }} />
<Text variant="caption">
<FormattedMessage id="PHOTO_OPTIMIZE_1" />
<Link
external
href="https://docs.wildme.org/docs/researchers/photography_guidelines"
>
<FormattedMessage id="PHOTO_OPTIMIZE_2" />
</Link>
<FormattedMessage id="PHOTO_OPTIMIZE_3" />
</Text>
</div>
</Grid>
<Grid item>
<Button
id={continueButtonText}
display="primary"
disabled={uploadInProgress}
onClick={async () => {
window.scrollTo(0, 0);
setReporting(true);
}}
style={{ marginTop: 16 }}
/>
</Grid>
</>
)}
</ReportSightingsPage>
);
}
I suspect that the problem is that useEffect is triggering a re-render of UploadManager, but
- I am not sure whether that's true
- I am looking for a good strategy for preventing said re-render. Should I somehow use
disabled=reporting
on the Upload Manager logic? Should I limit what fires off the useEffect hook? If so, how, specifically, might I do that? Can I blacklist things (e.g.,reporting
) from firing a useEffect?
Here is the branch of the repository I'm working from for more reference if that's needed. Creating an example on stackblitz proved non-trivial.
Many thanks in advance for any suggestions!