我可以在我的 react 应用 上使用Uppy上传图片:
选择并上传图像后,我会看到图像的预览,以及删除、添加更多等选项:
点击“继续”按钮后,reporting
(from const [reporting, setReporting] = useState(false);
) 的状态设置为 true,我认为这会触发 DOM 的重新渲染,包括UploadManager
组件的消失,其中包含上图所示的 Uppy 仪表板。现在,相反,<ReportForm>
渲染了一个组件:
...
{reporting ? (
<ReportForm assetReferences={files} exifData={exifData} />
) : (
<>
<Grid item style={{ marginTop: 20 }}>
<UploadManager
onUploadStarted={() => setUploadInProgress(true)}
onUploadComplete={() => setUploadInProgress(false)}
...
当用户单击组件中的“返回照片” <ReportForm>
(见上图)时,它只是将返回状态重置reporting
为 false。但是,Uppy 仪表板现在再次显示其默认设置“将文件拖放到此处或浏览文件”(参见第一张图片)。我想查看我刚刚上传的图像的预览。
我对 React 很陌生,但我可以看到 uppyInstance 是在 useEffect 挂钩中创建的,它似乎在reporting
状态更改时被调用。我怀疑这就是“重置” uppy 仪表板的原因(请参阅下面与 uppy 相关的组件的代码):
上传管理器.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>
);
}
以下是 UploadManager 所在的父组件的代码:
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>
);
}
我怀疑问题是 useEffect 正在触发 UploadManager 的重新渲染,但是
- 我不确定这是否属实
- 我正在寻找一种防止上述重新渲染的好策略。我应该以某种方式使用
disabled=reporting
上传管理器逻辑吗?我应该限制什么触发 useEffect 钩子吗?如果是这样,具体来说,我该怎么做?我可以将reporting
触发 useEffect 的事物(例如 )列入黑名单吗?
这是我正在使用的存储库的分支,以便在需要时提供更多参考。事实证明,在 stackblitz 上创建示例并非易事。
非常感谢您的任何建议!