将选定的文件保持在反应状态
Keep selected files in react state
我有以下问题:我创建了一个自定义放置组件,它接受一个文件和一个处理程序以及一个默认预览,它也使用 react-dropzone,我现在面临的是当我想有多个 FileUpload 字段,每次我更改一个时,另一个都会设置为空。
这是我的组件:
import React, {useState, useCallback, useEffect} from 'react';
import {useDropzone} from "react-dropzone";
import PropTypes from 'prop-types';
import './styles/style.css';
const FileIcon = ({extension}) => {
return (
<div className="position-relative">
<svg xmlns="http://www.w3.org/2000/svg" width="76" height="76" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"
className="feather feather-file">
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
<polyline points="13 2 13 9 20 9"/>
</svg>
<span className="position-absolute file-ext">{extension}</span>
</div>
)
}
const UploadIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none"
stroke="#cfcfcf" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"
className="feather feather-upload-cloud">
<polyline points="16 16 12 12 8 16"/>
<line x1="12" y1="12" x2="12" y2="21"/>
<path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/>
<polyline points="16 16 12 12 8 16"/>
</svg>
);
}
const CustomDropify = (props) => {
const {maxSize, accept, file, onSelect, defaultPreview} = props;
const [preview, setPreview] = useState(null);
const [fileObj, setFileObj] = useState(null);
const handleDrop = useCallback((acceptedFiles) => {
const file = acceptedFiles[0];
onSelect(file);
}, []);
const {getInputProps, getRootProps} = useDropzone({
onDrop: handleDrop,
maxSize,
maxFiles: 1,
accept
});
const setDefaultPreview = () => {
const filename = defaultPreview.split('/').reverse()[0];
const extension = filename.split('.').reverse()[0];
const isImage = ['jpg', 'gif', 'jpeg', 'ico', 'webp', 'png'].includes(extension);
setFileObj({
filename,
extension,
hasPreview: isImage
});
isImage ? setPreview(defaultPreview) : setPreview(null);
};
const handleRemove = e => {
e.stopPropagation();
onSelect(null);
}
useEffect(() => {
if (file) {
const isImage = file.type.includes('image');
setFileObj({
filename: file.name,
extension: file.name.split('.').reverse()[0],
hasPreview: isImage
})
isImage ? setPreview(URL.createObjectURL(file)) : setPreview(null);
return;
}
setFileObj(null);
setPreview(null);
if (!file && defaultPreview) setDefaultPreview();
}, [file]);
return (
<div className="custom_dropper_wrapper" {...getRootProps()}>
<input {...getInputProps()} />
<div className="custom_dropper_body">
{fileObj ? (
<>
{fileObj.hasPreview ?
<img src={preview} alt={fileObj.filename} className="custom_dropper_preview"/> : (
<h4>
<FileIcon extension={fileObj.extension}/>
</h4>
)}
</>
) : (
<>
<h4>
<UploadIcon/>
</h4>
<p>Drag and drop a file here or click</p>
</>
)}
</div>
{fileObj ? (
<div className="custom_dropper_fader">
<h4>{fileObj.filename}</h4>
<p>Drag and drop a file here or click</p>
{file && <button className="custom_dropper_remove_btn" onClick={handleRemove}>Remove</button>}
</div>
) : null}
</div>
)
}
CustomDropify.propTypes = {
maxSize: PropTypes.number,
accept: PropTypes.string,
onSelect: PropTypes.func.isRequired
}
export default CustomDropify;
这是一个示例用法:
...
const [images, setImages] = useState({
image1: null,
image2: null
});
...
<CustomDropify
file={images.image1}
onSelect={file => setImages({...images, image1:file})}
defaultPreview={'http://example.com/image.jpg'}
/>
...
<CustomDropify
file={images.image2}
onSelect={file => setImages({...images, image2:file})}
defaultPreview={'http://example.com/another.jpg'}
/>
现在,如果我在第一个字段中 select 一个文件,它可以工作,但是当我去 select 第二个字段中的另一个文件时,第一个字段被设置为 null
您的问题是您在调用 CustomDropify 的 onSelect
属性时使用了图像的先前状态,但方式不正确。所以请将您的第二部分代码更改为以下代码:
<CustomDropify
file={images.image1}
onSelect={file => setImages((file) => setImages(prev=> {return { ...prev, image1: file }})}
defaultPreview={'http://example.com/image.jpg'}
/>
...
<CustomDropify
file={images.image1}
onSelect={file => setImages((file) => setImages(prev=> {return { ...prev, image2: file }})}
defaultPreview={'http://example.com/image.jpg'}
/>
我有以下问题:我创建了一个自定义放置组件,它接受一个文件和一个处理程序以及一个默认预览,它也使用 react-dropzone,我现在面临的是当我想有多个 FileUpload 字段,每次我更改一个时,另一个都会设置为空。
这是我的组件:
import React, {useState, useCallback, useEffect} from 'react';
import {useDropzone} from "react-dropzone";
import PropTypes from 'prop-types';
import './styles/style.css';
const FileIcon = ({extension}) => {
return (
<div className="position-relative">
<svg xmlns="http://www.w3.org/2000/svg" width="76" height="76" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"
className="feather feather-file">
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
<polyline points="13 2 13 9 20 9"/>
</svg>
<span className="position-absolute file-ext">{extension}</span>
</div>
)
}
const UploadIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none"
stroke="#cfcfcf" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"
className="feather feather-upload-cloud">
<polyline points="16 16 12 12 8 16"/>
<line x1="12" y1="12" x2="12" y2="21"/>
<path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/>
<polyline points="16 16 12 12 8 16"/>
</svg>
);
}
const CustomDropify = (props) => {
const {maxSize, accept, file, onSelect, defaultPreview} = props;
const [preview, setPreview] = useState(null);
const [fileObj, setFileObj] = useState(null);
const handleDrop = useCallback((acceptedFiles) => {
const file = acceptedFiles[0];
onSelect(file);
}, []);
const {getInputProps, getRootProps} = useDropzone({
onDrop: handleDrop,
maxSize,
maxFiles: 1,
accept
});
const setDefaultPreview = () => {
const filename = defaultPreview.split('/').reverse()[0];
const extension = filename.split('.').reverse()[0];
const isImage = ['jpg', 'gif', 'jpeg', 'ico', 'webp', 'png'].includes(extension);
setFileObj({
filename,
extension,
hasPreview: isImage
});
isImage ? setPreview(defaultPreview) : setPreview(null);
};
const handleRemove = e => {
e.stopPropagation();
onSelect(null);
}
useEffect(() => {
if (file) {
const isImage = file.type.includes('image');
setFileObj({
filename: file.name,
extension: file.name.split('.').reverse()[0],
hasPreview: isImage
})
isImage ? setPreview(URL.createObjectURL(file)) : setPreview(null);
return;
}
setFileObj(null);
setPreview(null);
if (!file && defaultPreview) setDefaultPreview();
}, [file]);
return (
<div className="custom_dropper_wrapper" {...getRootProps()}>
<input {...getInputProps()} />
<div className="custom_dropper_body">
{fileObj ? (
<>
{fileObj.hasPreview ?
<img src={preview} alt={fileObj.filename} className="custom_dropper_preview"/> : (
<h4>
<FileIcon extension={fileObj.extension}/>
</h4>
)}
</>
) : (
<>
<h4>
<UploadIcon/>
</h4>
<p>Drag and drop a file here or click</p>
</>
)}
</div>
{fileObj ? (
<div className="custom_dropper_fader">
<h4>{fileObj.filename}</h4>
<p>Drag and drop a file here or click</p>
{file && <button className="custom_dropper_remove_btn" onClick={handleRemove}>Remove</button>}
</div>
) : null}
</div>
)
}
CustomDropify.propTypes = {
maxSize: PropTypes.number,
accept: PropTypes.string,
onSelect: PropTypes.func.isRequired
}
export default CustomDropify;
这是一个示例用法:
...
const [images, setImages] = useState({
image1: null,
image2: null
});
...
<CustomDropify
file={images.image1}
onSelect={file => setImages({...images, image1:file})}
defaultPreview={'http://example.com/image.jpg'}
/>
...
<CustomDropify
file={images.image2}
onSelect={file => setImages({...images, image2:file})}
defaultPreview={'http://example.com/another.jpg'}
/>
现在,如果我在第一个字段中 select 一个文件,它可以工作,但是当我去 select 第二个字段中的另一个文件时,第一个字段被设置为 null
您的问题是您在调用 CustomDropify 的 onSelect
属性时使用了图像的先前状态,但方式不正确。所以请将您的第二部分代码更改为以下代码:
<CustomDropify
file={images.image1}
onSelect={file => setImages((file) => setImages(prev=> {return { ...prev, image1: file }})}
defaultPreview={'http://example.com/image.jpg'}
/>
...
<CustomDropify
file={images.image1}
onSelect={file => setImages((file) => setImages(prev=> {return { ...prev, image2: file }})}
defaultPreview={'http://example.com/image.jpg'}
/>