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>
</>
);
}
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:
<input
// To make sure your ID is unique, you can pass the type, for example
id={type}
type="file"
className="hidden"
onChange={handleMediaChange}
/>
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:
<label
htmlFor={type}
>
.....
</label>
And inside your page.tsx
just pass the type
props to every of your InputMedia.tsx
component:
<InputMedia
media={formItems.results[tabIndexStore.resultTabIndex].resultMedia}
handleMediaChange={(e) => {
console.log("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.log("question type input change");
handleMediaChange(e, "result", tabIndexStore.resultTabIndex);
}}
// here's another type
type="question"
/>
The important point is that your ID
attributes cannot be identical.
Another example is to pass id
props from your page.tsx
like this:
<InputMedia
media={formItems.results[tabIndexStore.resultTabIndex].resultMedia}
handleMediaChange={(e) => handleMediaChange(e, "result", tabIndexStore.resultTabIndex)}
// will generate unique id over here
id={`resultMediaInput-${tabIndexStore.resultTabIndex}`}
/>