React 重复组件更新错误状态:钩子
React Duplicate components updating wrong state: hooks
我是反应新手,才用了几天,所以如果这是一个愚蠢的问题,请原谅我。
我有一个文件输入组件和一个图像缩略图组件,我使用两个重复的文件输入组件来更新两个不同的状态,然后在两个不同的缩略图组件中显示来自不同状态的图像。我在所有组件上设置了唯一键,但只有 Dom 中第一个组件的状态被更新。当我使用第二个文件输入添加图像时,它会更新属于第一个文件输入的状态。
我已经尝试寻找解决方案,所有解决方案都声明要使用唯一密钥,我认为我做得很好。
let [certification, setCertification] = useState(null)
let [photoId, setPhotoId] = useState(null)
let handleUpdateCertificate = (e) =>{
let file = e.target.files[0]
console.log(file)
let path = URL.createObjectURL(file)
let newCertificate = {
'file': file,
'path' : path
}
setCertification(newCertificate)
}
let handleUpdatePhotoId = (e) => {
let file = e.target.photoidinput.files[0]
let path = URL.createObjectURL(file)
let newPhotoID = {
'file': file,
'path' : path
}
setPhotoId(newPhotoID)
}
我的returnhtml是:
<div className='justify-content-center margin-20' key='certificate-wrapper'>
<ImgThumbnail key={'certificate'} name={'certificate'} image=
{certification?.path} wrapperClass={'justify-content-center margin-20'}/>
</div>
<div className='justify-content-center margin-20'>
<FileInput key={'certificateinput'} name={'certificateinput'} labelText={<p
className='text-paragraph edit-btn-text'>Add Certificate</p>}
onChange={handleUpdateCertificate}
classWrapper={'edit-profile-responsive-btn-wrapper'}/>
</div>
<div className='justify-content-center margin-20 ' key='photo-Id'>
<ImgThumbnail key={'photoid'} name={'photoId'} image={photoId?.path}
wrapperClass={'justify-content-center margin-20'}/>
</div>
<div className='justify-content-center margin-20' key='photo-id-input-wrapper'>
<FileInput key={'photoidinput'} name={'photoidinput'} labelText={<p
className='text-paragraph edit-btn-text'>Add Photo ID</p>}
onChange={handleUpdatePhotoId}
classWrapper={'edit-profile-responsive-btn-wrapper'}/>
</div>
好的,我会给你一些提示,然后给你一个工作示例:
如果您正在编写这样的 JSX 元素,则不需要设置 key 属性,只有当您呈现来自一个数组,防止数组更新时无用re-rendering。
使用 const 而不是 let 当变量是静态时,有一个关于它的 lint 规则!
尝试使用 DRY,你的更新处理程序共享很多逻辑,如果你要添加更多的输入,那将是所有代码重复。
现在代码:
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [certification, setCertification] = useState(null);
const [photoId, setPhotoId] = useState(null);
const updateData = (file, cb) => {
const path = URL.createObjectURL(file);
const data = {
file: file,
path: path,
};
cb(data);
};
const handleUpdateCertificate = (e) => {
updateData(e.target.files[0], setCertification);
};
const handleUpdatePhotoId = (e) => {
updateData(e.target.files[0], setPhotoId);
};
return (
<div>
{certification && (
<div className="justify-content-center margin-20">
<ImgThumbnail
name={'certificate'}
image={certification?.path}
wrapperClass={'justify-content-center margin-20'}
/>
</div>
)}
<div className="justify-content-center margin-20">
<FileInput
id="certificate"
name={'certificateinput'}
labelText={
<p className="text-paragraph edit-btn-text">Add Certificate</p>
}
onChange={handleUpdateCertificate}
classWrapper={'edit-profile-responsive-btn-wrapper'}
/>
</div>
{photoId && (
<div className="justify-content-center margin-20 " key="photo-Id">
<ImgThumbnail
name={'photoId'}
image={photoId?.path}
wrapperClass={'justify-content-center margin-20'}
/>
</div>
)}
<div
className="justify-content-center margin-20"
key="photo-id-input-wrapper"
>
<FileInput
id="photo"
name={'photoidinput'}
labelText={
<p className="text-paragraph edit-btn-text">Add Photo ID</p>
}
onChange={handleUpdatePhotoId}
classWrapper={'edit-profile-responsive-btn-wrapper'}
/>
</div>
</div>
);
}
const FileInput = ({ id, labelText, ...props }) => (
<label htmlFor={id}>
{labelText}
<input id={id} style={{ display: 'none' }} type="file" {...props} />
</label>
);
const ImgThumbnail = ({ name, image }) => (
<div>
<img style={{ width: '100px', height: '100px' }} src={image} alt={name} />
</div>
);
这个例子是正确的,你可能在 FileInput 组件中做错了,记住标签必须有一个 htmlFor 属性使用要触发的 input 元素的 id。
现在,可以优化此代码并使其更加 React 风格,因为您将来可能会有更多的文件输入,让我们看看如何通过创建可重用组件来优化它并正确组合它们:
import React, { useState } from 'react';
import './style.css';
/* INPUTS IMAGE TYPES */
const inputs = [
{ type: 'photo', name: 'photo', label: 'Photo' },
{ type: 'certificate', name: 'certificate', label: 'Certificate' },
{ type: 'anotherType', name: 'anotherName', label: 'Another Input' },
];
export default function App() {
return (
<div>
{inputs.map((data) => (
<ImagePreviewer key={data.type} data={data} />
))}
</div>
);
}
const FileInput = ({ id, labelText, ...props }) => (
<label htmlFor={id}>
{labelText}
<input id={id} style={{ display: 'none' }} type="file" {...props} />
</label>
);
const ImgThumbnail = ({ name, image }) => (
<div>
<img src={image} alt={name} />
</div>
);
const ImagePreviewer = ({ data: { type, name, label } }) => {
const [image, setImage] = useState(null);
const updateData = (file, cb) => {
const path = URL.createObjectURL(file);
const data = {
file: file,
path: path,
};
cb(data);
};
const handleUpdate = (e) => {
updateData(e.target.files[0], setImage);
};
return (
<div>
{image && (
<div>
<ImgThumbnail name={'name'} image={image?.path} />
</div>
)}
<div>
<FileInput
id={name}
name={name}
labelText={<p>Add {label}</p>}
onChange={handleUpdate}
/>
</div>
</div>
);
};
一个工作演示 HERE。
我是反应新手,才用了几天,所以如果这是一个愚蠢的问题,请原谅我。
我有一个文件输入组件和一个图像缩略图组件,我使用两个重复的文件输入组件来更新两个不同的状态,然后在两个不同的缩略图组件中显示来自不同状态的图像。我在所有组件上设置了唯一键,但只有 Dom 中第一个组件的状态被更新。当我使用第二个文件输入添加图像时,它会更新属于第一个文件输入的状态。
我已经尝试寻找解决方案,所有解决方案都声明要使用唯一密钥,我认为我做得很好。
let [certification, setCertification] = useState(null)
let [photoId, setPhotoId] = useState(null)
let handleUpdateCertificate = (e) =>{
let file = e.target.files[0]
console.log(file)
let path = URL.createObjectURL(file)
let newCertificate = {
'file': file,
'path' : path
}
setCertification(newCertificate)
}
let handleUpdatePhotoId = (e) => {
let file = e.target.photoidinput.files[0]
let path = URL.createObjectURL(file)
let newPhotoID = {
'file': file,
'path' : path
}
setPhotoId(newPhotoID)
}
我的returnhtml是:
<div className='justify-content-center margin-20' key='certificate-wrapper'>
<ImgThumbnail key={'certificate'} name={'certificate'} image=
{certification?.path} wrapperClass={'justify-content-center margin-20'}/>
</div>
<div className='justify-content-center margin-20'>
<FileInput key={'certificateinput'} name={'certificateinput'} labelText={<p
className='text-paragraph edit-btn-text'>Add Certificate</p>}
onChange={handleUpdateCertificate}
classWrapper={'edit-profile-responsive-btn-wrapper'}/>
</div>
<div className='justify-content-center margin-20 ' key='photo-Id'>
<ImgThumbnail key={'photoid'} name={'photoId'} image={photoId?.path}
wrapperClass={'justify-content-center margin-20'}/>
</div>
<div className='justify-content-center margin-20' key='photo-id-input-wrapper'>
<FileInput key={'photoidinput'} name={'photoidinput'} labelText={<p
className='text-paragraph edit-btn-text'>Add Photo ID</p>}
onChange={handleUpdatePhotoId}
classWrapper={'edit-profile-responsive-btn-wrapper'}/>
</div>
好的,我会给你一些提示,然后给你一个工作示例:
如果您正在编写这样的 JSX 元素,则不需要设置 key 属性,只有当您呈现来自一个数组,防止数组更新时无用re-rendering。
使用 const 而不是 let 当变量是静态时,有一个关于它的 lint 规则!
尝试使用 DRY,你的更新处理程序共享很多逻辑,如果你要添加更多的输入,那将是所有代码重复。
现在代码:
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [certification, setCertification] = useState(null);
const [photoId, setPhotoId] = useState(null);
const updateData = (file, cb) => {
const path = URL.createObjectURL(file);
const data = {
file: file,
path: path,
};
cb(data);
};
const handleUpdateCertificate = (e) => {
updateData(e.target.files[0], setCertification);
};
const handleUpdatePhotoId = (e) => {
updateData(e.target.files[0], setPhotoId);
};
return (
<div>
{certification && (
<div className="justify-content-center margin-20">
<ImgThumbnail
name={'certificate'}
image={certification?.path}
wrapperClass={'justify-content-center margin-20'}
/>
</div>
)}
<div className="justify-content-center margin-20">
<FileInput
id="certificate"
name={'certificateinput'}
labelText={
<p className="text-paragraph edit-btn-text">Add Certificate</p>
}
onChange={handleUpdateCertificate}
classWrapper={'edit-profile-responsive-btn-wrapper'}
/>
</div>
{photoId && (
<div className="justify-content-center margin-20 " key="photo-Id">
<ImgThumbnail
name={'photoId'}
image={photoId?.path}
wrapperClass={'justify-content-center margin-20'}
/>
</div>
)}
<div
className="justify-content-center margin-20"
key="photo-id-input-wrapper"
>
<FileInput
id="photo"
name={'photoidinput'}
labelText={
<p className="text-paragraph edit-btn-text">Add Photo ID</p>
}
onChange={handleUpdatePhotoId}
classWrapper={'edit-profile-responsive-btn-wrapper'}
/>
</div>
</div>
);
}
const FileInput = ({ id, labelText, ...props }) => (
<label htmlFor={id}>
{labelText}
<input id={id} style={{ display: 'none' }} type="file" {...props} />
</label>
);
const ImgThumbnail = ({ name, image }) => (
<div>
<img style={{ width: '100px', height: '100px' }} src={image} alt={name} />
</div>
);
这个例子是正确的,你可能在 FileInput 组件中做错了,记住标签必须有一个 htmlFor 属性使用要触发的 input 元素的 id。
现在,可以优化此代码并使其更加 React 风格,因为您将来可能会有更多的文件输入,让我们看看如何通过创建可重用组件来优化它并正确组合它们:
import React, { useState } from 'react';
import './style.css';
/* INPUTS IMAGE TYPES */
const inputs = [
{ type: 'photo', name: 'photo', label: 'Photo' },
{ type: 'certificate', name: 'certificate', label: 'Certificate' },
{ type: 'anotherType', name: 'anotherName', label: 'Another Input' },
];
export default function App() {
return (
<div>
{inputs.map((data) => (
<ImagePreviewer key={data.type} data={data} />
))}
</div>
);
}
const FileInput = ({ id, labelText, ...props }) => (
<label htmlFor={id}>
{labelText}
<input id={id} style={{ display: 'none' }} type="file" {...props} />
</label>
);
const ImgThumbnail = ({ name, image }) => (
<div>
<img src={image} alt={name} />
</div>
);
const ImagePreviewer = ({ data: { type, name, label } }) => {
const [image, setImage] = useState(null);
const updateData = (file, cb) => {
const path = URL.createObjectURL(file);
const data = {
file: file,
path: path,
};
cb(data);
};
const handleUpdate = (e) => {
updateData(e.target.files[0], setImage);
};
return (
<div>
{image && (
<div>
<ImgThumbnail name={'name'} image={image?.path} />
</div>
)}
<div>
<FileInput
id={name}
name={name}
labelText={<p>Add {label}</p>}
onChange={handleUpdate}
/>
</div>
</div>
);
};
一个工作演示 HERE。