ОБНОВЛЕНИЕ: я создал коды и ящик для своей проблемы. Вы увидите 3 компонента, в которые вы можете загружать файлы, но всякий раз, когда вы выбираете носитель, всегда будет обновляться первый компонент. https://codesandbox.io/p/devbox/vigorous-joana-rzqwcv
Я использую последнюю версию Next.js. У меня есть компонент под названием «InputMedia». Я использую этот компонент 3 раза на странице с разными реквизитами. Моя проблема: обновление состояния всегда выполняется с использованием информации первого компонента. Несмотря на то, что я нажимаю третий компонент, состояние первого компонента всегда меняется. Я пытался отправить тип в качестве реквизита, не получилось. Я пытался отправить важные вещи, но аргументы снова не сработали.
Я всегда играл с функцией handleMediaChange, реквизитами InputMedia и компонентом InputMedia.
Я буду рад, если смогу узнать, чего мне не хватает.
Вот моя страница:
"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.info(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.info("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>
</>
);
}
Я добавил коды и ящик @cantdocpp
Я проверил ваши коды и ящик и увидел, что проблемы связаны с вашим компонентом InputMedia.tsx
.
Если вы хотите использовать атрибут id
для каждого из ваших компонентов, вам необходимо убедиться, что каждый идентификатор внутри вашего приложения уникален.
Например, в вашем компоненте InputMedia.tsx
:
<input
// To make sure your ID is unique, you can pass the type, for example
id = {type}
type = "file"
className = "hidden"
onChange = {handleMediaChange}
/>
И если каждый ваш ввод уже имеет уникальный идентификатор, вам также необходимо убедиться, что ваша метка также указывает на правильный идентификатор, например, внутри вашего InputMedia.tsx
компонента:
<label
htmlFor = {type}
>
.....
</label>
А внутри вашего page.tsx
просто передайте реквизиты type
каждому вашему компоненту InputMedia.tsx
:
<InputMedia
media = {formItems.results[tabIndexStore.resultTabIndex].resultMedia}
handleMediaChange = {(e) => {
console.info("result type input change");
handleMediaChange(e, "result", tabIndexStore.resultTabIndex);
}}
// pass the type here for example
type = "result"
/>
<InputMedia
media = {formItems.results[tabIndexStore.resultTabIndex].resultMedia}
handleMediaChange = {(e) => {
console.info("question type input change");
handleMediaChange(e, "result", tabIndexStore.resultTabIndex);
}}
// here's another type
type = "question"
/>
Важным моментом является то, что ваши атрибуты ID
не могут быть идентичными.
Другой пример — передать реквизиты id
из вашего page.tsx
следующим образом:
<InputMedia
media = {formItems.results[tabIndexStore.resultTabIndex].resultMedia}
handleMediaChange = {(e) => handleMediaChange(e, "result", tabIndexStore.resultTabIndex)}
// will generate unique id over here
id = {`resultMediaInput-${tabIndexStore.resultTabIndex}`}
/>
Все в вашем коде правильно, кроме компонента InputMedia. Здесь вы ссылаетесь на метку к одному и тому же идентификатору ввода для всех ваших экземпляров ввода. Таким образом, какие бы входные данные вы ни вносили, они нацелены только на первый. Вам просто нужно настроить атрибут тега метки for
на идентификатор ввода, который отличается для каждого экземпляра ввода. Ваш скорректированный код должен выглядеть так:
Ваша форма:
<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")}
inputType = "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)
}
inputType = "question"
/* 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)
}
inputType = "result"
/* type = "result"
index = {tabIndexStore.resultTabIndex} */
/>
<Button type = "button" isButtonSecondary = {true} name = "Add new result" />
<br />
<br />
<Button type = "button" isButtonSecondary = {false} name = "Submit" />
</form>
Ваш компонент InputMedia:
export default function InputMedia({
media,
inputType,
handleMediaChange,
}) {
return (
<>
Media
<label
htmlFor=`dropzone-file-${inputType}`
>
<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
name = {inputType}
id=`dropzone-file-${inputType}`
type = "file"
className = "hidden"
onChange = {handleMediaChange}
/>
</label>
</>
);
}
Я думаю, вам лучше сделать коды и ящик для вашей проблемы. Потому что только в вашем вопросе слишком много компонентов и функций.