Использование одних и тех же компонентов на странице приводит к ошибкам в Next.js

ОБНОВЛЕНИЕ: я создал коды и ящик для своей проблемы. Вы увидите 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 26.06.2024 14:20

Я добавил коды и ящик @cantdocpp

Ozan 26.06.2024 14:43
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
2
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я проверил ваши коды и ящик и увидел, что проблемы связаны с вашим компонентом 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>
</>
 );
}

Другие вопросы по теме