UPDATE: I created a codesandbox for my issue. You will see 3 components that you can upload files but whenever you choose a media, always the first component will update.
https://codesandbox.io/p/devbox/vigorous-joana-rzqwcv
I use Next.js latest version. I have a component called "InputMedia". I use this component 3 times in a page with different props. My problem is: State update is always being done by the first component’s information. Even though I click 3rd component always the first component’s state changes. I tried to send type as a prop, didn’t work. I tried to send important things as arguments didn’t work again.
I alwas played with handleMediaChange function, InputMedia props and InputMedia component.
I will be glad if I can learn what am I missing.
Here is my Page:
"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
}
/> */
}
And InputMedia component:
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>
</>
);
}
2
Answers
I’ve checked your codesandbox and see the problems comes from your
InputMedia.tsx
component.If you want to use
id
attribute to every one of your components, then you have to make sure that every ID you have inside your app is unique.For example, in your
InputMedia.tsx
component:And if your every input already have unique ID, you also need to make sure that your label also point to the correct ID, for example, inside your
InputMedia.tsx
component:And inside your
page.tsx
just pass thetype
props to every of yourInputMedia.tsx
component:The important point is that your
ID
attributes cannot be identical.Another example is to pass
id
props from yourpage.tsx
like this:Everything in your code is correct except the component InputMedia. There you are referring the label to same id of input for all of your input instances. So in whichever input you make a change, it only targets for the first one. You just need to make your label tag
for
attribute target the input id that is different for each instance of input. Your adjusted code should look like this:Your form:
Your InputMedia Component: