Next.js复用InputMedia组件引发状态更新异常问题求助
问题:多个InputMedia组件上传文件时仅第一个组件状态更新
页面中有3个可上传文件的InputMedia组件,但无论选择哪个组件上传媒体,始终只有第一个组件的状态会更新。使用最新版Next.js,将InputMedia组件复用3次并传入不同props,状态更新始终仅对应第一个组件的信息,即使点击第三个组件,也是第一个组件的状态发生变化。尝试过传入type作为props、将关键信息作为参数传递,均无效。
Manage页面代码
"use client"; import Button from "@/components/Button/Button"; import Input from "@/components/Input/Input"; import InputMedia from "@/components/InputMedia/InputMedia"; import Select from "@/components/Select/Select"; import Tab from "@/components/Tab/Tab"; import { useState } from "react"; export default function Manage() { const [formItems, setFormItems] = useState({ title: "", description: "", category: "Psikoloji Testleri", mainMedia: {}, mainMediaUrl: "", questions: [ { questionTitle: "selam", questionMedia: {}, questionMediaUrl: "", options: [ { answer: "", points: [{ resultTitle: "", point: 0, id: "item1" }] }, ], }, ], results: [ { resultTitle: "", resultDescription: "", resultMedia: {}, resultMediaUrl: "", }, ], }); const [tabIndexStore, setTabIndexStore] = useState({ questionTabIndex: 0, questionOptionTabIndex: 0, resultTabIndex: 0, }); const handleMainPropertiesChange = (e) => { const { name, value } = e.target; setFormItems((prev) => ({ ...prev, [name]: value, })); }; const handleMediaChange = (e, type, index = null) => { console.log(type, index, "function"); const { files } = e.target; if (!files || files.length === 0) return; const file = files[0]; setFormItems((prev) => { let updatedItems = { ...prev }; switch (type) { case "main": updatedItems.mainMedia = file; break; case "question": if (index !== null && index < updatedItems.questions.length) { updatedItems.questions[index].questionMedia = file; } break; case "result": if (index !== null && index < updatedItems.results.length) { updatedItems.results[index].resultMedia = file; } break; default: break; } return updatedItems; }); }; const handleQuestionChange = (e, index) => { const { name, value } = e.target; const newQuestions = [...formItems.questions]; newQuestions[index][name] = value; setFormItems((prev) => ({ ...prev, questions: newQuestions, })); }; const updateTabIndex = (index, item) => { setTabIndexStore((prev) => ({ ...prev, [item]: index, })); }; return ( <form> <Input name="title" type="text" handleFunction={handleMainPropertiesChange} value={formItems.title} /> <hr /> <Input name="description" type="text" handleFunction={handleMainPropertiesChange} value={formItems.description} /> <hr /> <Select name="category" handleFunction={handleMainPropertiesChange} value={formItems.category} /> <hr /> <InputMedia media={formItems.mainMedia} handleMediaChange={(e) => handleMediaChange(e, "main")} /* handleMediaChange={handleMediaChange} type="main" */ /> <hr /> <h1>Questions</h1> <Tab items={formItems.questions} handleFunction={updateTabIndex} currentIndex={tabIndexStore.questionTabIndex} itemName="questionTabIndex" /> <Input name="questionTitle" type="text" handleFunction={handleQuestionChange} index={tabIndexStore.questionTabIndex} value={ formItems.questions[tabIndexStore.questionTabIndex].questionTitle } /> <InputMedia media={ formItems.questions[tabIndexStore.questionTabIndex].questionMedia } handleMediaChange={(e) => handleMediaChange(e, "question", tabIndexStore.questionTabIndex) } /* handleMediaChange={handleMediaChange} type="question" index={tabIndexStore.questionTabIndex} */ /> <h1>Question Options</h1> <Tab items={formItems.questions[tabIndexStore.questionTabIndex].options} handleFunction={updateTabIndex} currentIndex={tabIndexStore.questionOptionTabIndex} itemName="questionOptionTabIndex" /> <Input name="option" type="text" /> <Input name="point" type="number" /> <Button type="button" isButtonSecondary={true} name="Add new option" /> <br /> <br /> <Button type="button" isButtonSecondary={true} name="Add new question" /> <hr /> <h1>Results</h1> <Tab items={formItems.results} handleFunction={updateTabIndex} currentIndex={tabIndexStore.resultTabIndex} itemName="resultTabIndex" /> <Input name="result title" type="text" /> <Input name="result description" type="text" /> <InputMedia media={formItems.results[tabIndexStore.resultTabIndex].resultMedia} handleMediaChange={(e) => handleMediaChange(e, "result", tabIndexStore.resultTabIndex) } /* type="result" index={tabIndexStore.resultTabIndex} */ /> <Button type="button" isButtonSecondary={true} name="Add new result" /> <br /> <br /> <Button type="button" isButtonSecondary={false} name="Submit" /> </form> ); } { /* <Input name="questionMedia" type="file" handleFunction={handleQuestionChange} index={tabIndexStore.questionTabIndex} value={ formItems.questions[tabIndexStore.questionTabIndex].questionMedia } /> */ }
InputMedia组件代码
export default function InputMedia({ media, handleMediaChange, /* type, index = null, */ }) { return ( <> Media <label htmlFor="dropzone-file" className="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600" onClick={(e) => { e.preventDefault(); console.log("uffufu"); }} > <div className="flex flex-col items-center justify-center pt-5 pb-6"> <svg className="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16" > <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2" /> </svg> <p className="mb-2 text-sm text-gray-500 dark:text-gray-400"> <span className="font-semibold">Click to upload</span> or drag and drop </p> {media instanceof File && ( <p className="text-xs text-gray-500 dark:text-gray-400"> Uploaded media name: {media.name} </p> )} </div> <input id="dropzone-file" type="file" className="hidden" onChange={handleMediaChange} /> </label> </> ); }
解决方案
问题根源在于所有InputMedia组件中的file input使用了相同的id dropzone-file。在HTML中,id必须是唯一的,当多个元素共享同一个id时,点击label或触发input时,浏览器只会找到第一个匹配该id的元素,导致始终只有第一个InputMedia的状态被更新。
修复步骤:
- 给每个InputMedia组件传入唯一的id属性
- 在InputMedia组件中使用传入的id来关联label和input
修改Manage页面中InputMedia的调用
// 第一个InputMedia <InputMedia media={formItems.mainMedia} handleMediaChange={(e) => handleMediaChange(e, "main")} id="main-media-dropzone" /> // 第二个InputMedia(Question部分) <InputMedia media={formItems.questions[tabIndexStore.questionTabIndex].questionMedia} handleMediaChange={(e) => handleMediaChange(e, "question", tabIndexStore.questionTabIndex)} id={`question-media-dropzone-${tabIndexStore.questionTabIndex}`} /> // 第三个InputMedia(Result部分) <InputMedia media={formItems.results[tabIndexStore.resultTabIndex].resultMedia} handleMediaChange={(e) => handleMediaChange(e, "result", tabIndexStore.resultTabIndex)} id={`result-media-dropzone-${tabIndexStore.resultTabIndex}`} />
修改InputMedia组件
export default function InputMedia({ media, handleMediaChange, id, // 接收id属性 }) { return ( <> Media <label htmlFor={id} // 使用传入的id关联label className="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600" onClick={(e) => { e.preventDefault(); console.log("uffufu"); }} > <div className="flex flex-col items-center justify-center pt-5 pb-6"> <svg className="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16" > <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2" /> </svg> <p className="mb-2 text-sm text-gray-500 dark:text-gray-400"> <span className="font-semibold">Click to upload</span> or drag and drop </p> {media instanceof File && ( <p className="text-xs text-gray-500 dark:text-gray-400"> Uploaded media name: {media.name} </p> )} </div> <input id={id} // 使用传入的id作为input的id type="file" className="hidden" onChange={handleMediaChange} /> </label> </> ); }
这样每个file input都有了唯一的id,label和input的关联关系正确,点击不同的InputMedia组件时,会触发对应的input元素,状态更新也会对应到正确的组件上。
内容的提问来源于stack exchange,提问作者Ozan
相关产品推荐
相关产品推荐

