Копирование элемента рядом с элементом соответственно не работает

У меня есть массив объектов следующего формата

const [type1Options, setType1Options] = useState([
  {
    name: "Name1",
    value: "Value1",
  },
  {
    name: "Name2",
    value: "Value2",
  },
]);

const [type2Options, setType2Options] = useState([
  {
    name: "Name1",
    value: "Value1",
  },
  {
    name: "Name2",
    value: "Value2",
  },
]);

Я визуализирую эти объекты по категориям с помощью кнопок копирования и удаления для каждой записи. Удалить удалит запись из массива, а копирование скопирует содержимое выбранных элементов в новую запись и разместит ее прямо под выбранной записью. Удаление работает нормально, но копирование последней записи не работает должным образом. Может ли кто-нибудь помочь?

Песочница: https://codesandbox.io/p/sandbox/i18-demo-7594gf?file=%2Fsrc%2FApp.js%3A5%2C2-24%2C6

Утилиты для функций копирования и удаления

export const deleteItems = (list, idx) => {
  const temp = [...list];
  temp.splice(idx, 1);
  return temp;
};

export const copyItems = (list, idx) => {
  const newItem = { ...list[idx] };
  const newItems = [...list.slice(0, idx + 1), newItem, ...list.slice(idx + 1)];
  return newItems;
};
import { useState } from "react";
import List from "./List";

export default function App() {
  const [type1Options, setType1Options] = useState([
    {
      name: "Name1",
      value: "Value1",
    },
    {
      name: "Name2",
      value: "Value2",
    },
  ]);

  const [type2Options, setType2Options] = useState([
    {
      name: "Name1",
      value: "Value1",
    },
    {
      name: "Name2",
      value: "Value2",
    },
  ]);

  return (
    <div>
      <List
        type1Options = {type1Options}
        type2Options = {type2Options}
        setType1Options = {setType1Options}
        setType2Options = {setType2Options}
      />
    </div>
  );
}
import Type1 from "./Type1";
import Type2 from "./Type2";

export default function List(props) {
  const { type1Options, type2Options, setType1Options, setType2Options } =
    props;

  return (
    <>
      <div>
        Category 1
        {type1Options.map((obj, index) => (
          <Type1
            index = {index}
            obj = {obj}
            type1Options = {type1Options}
            setType1Options = {setType1Options}
          />
        ))}
      </div>
      <br />
      <div>
        Category 2
        {type2Options.map((obj, index) => (
          <Type2
            index = {index}
            obj = {obj}
            type2Options = {type2Options}
            setType1Options = {setType2Options}
          />
        ))}
      </div>
    </>
  );
}
import "./styling.css";
import { deleteItems, copyItems } from "./utils";

export default function Type1(props) {
  const { index, obj, type1Options, setType1Options } = props;

  const copyHandler = () => setType1Options(copyItems(type1Options, index + 1));

  const deleteHandler = (index) =>
    setType1Options(deleteItems(type1Options, index + 1));

  return (
    <div className = "box-container">
      <div className = "box-header">
        <h3>Index {index + 1}</h3>
        <div className = "box-buttons">
          <button onClick = {copyHandler}>Copy</button>
          <button onClick = {deleteHandler}>Delete</button>
        </div>
      </div>
      <div className = "box-content">
        {obj.name}: {obj.value}
      </div>
    </div>
  );
}
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
0
57
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Проблема заключалась в несоответствии 1 и 2 в type1Options и type2Options в 2 файлах. вот обновленный код.

Список.jsx

import Type1 from "./Type1";
import Type2 from "./Type2";

export default function List(props) {
  const { type1Options, type2Options, setType1Options, setType2Options } =
    props;

  return (
    <>
      <div>
        Category 1
        {type1Options.map((obj, index) => (
          <Type1
            index = {index}
            obj = {obj}
            type1Options = {type1Options}
            setType1Options = {setType1Options}
          />
        ))}
      </div>
      <br />
      <div>
        Category 2
        {type2Options.map((obj, index) => (
          <Type2
            index = {index}
            obj = {obj}
            type2Options = {type2Options}
            setType2Options = {setType2Options}
          />
        ))}
      </div>
    </>
  );
}

и type2.jsx

import React from "react";
import "./styling.css";
import { deleteItems, copyItems } from "./utils";

export default function Type2(props) {
  const { index, obj, type2Options, setType2Options } = props;

  const copyHandler = () => setType2Options(copyItems(type2Options, index));

  const deleteHandler = () => setType2Options(deleteItems(type2Options, index));

  return (
    <div className = "box-container">
      <div className = "box-header">
        <h3>Index {index + 1}</h3>
        <div className = "box-buttons">
          <button onClick = {copyHandler}>Copy</button>
          <button onClick = {deleteHandler}>Delete</button>
        </div>
      </div>
      <div className = "box-content">
        {obj.name}: {obj.value}
      </div>
    </div>
  );
}

Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, как и/или почему он решает проблему, улучшит долгосрочную ценность ответа.

Drew Reese 04.09.2024 01:57

@DrewReese, ок, я отредактировал свой ответ.

Rohit Rajput 04.09.2024 12:21

Проблемы

  • Компонент Type2 передает index + 1 служебным функциям и копирует/удаляет неправильный элемент массива.

    const copyHandler = () =>
      setType1Options(copyItems(type2Options, index + 1));
    
    const deleteHandler = (index) =>
      setType1Options(deleteItems(type2Options, index + 1));
    
  • Компоненты Type1 и Type2 не могут передать индекс массива служебной функции delete, вместо этого они передают объект события onClick, и из исходного массива удаляется неправильный элемент.

    const deleteHandler = (index) => // <-- onClick event object
      setType1Options(deleteItems(type1Options, index));
    
    ...
    
    <button onClick = {deleteHandler}>Delete</button>
    

Решение/Предложения

  • Передайте текущее значение индекса, чтобы правильно скопировать или удалить элемент массива по этому индексу.
  • Не передавайте объект события onClick в служебную функцию.
const copyHandler = () =>
  setType1Options(copyItems(type2Options, index));

const deleteHandler = () =>
  setType1Options(deleteItems(type2Options, index)); // <-- index from props

Дополнительные советы/исправления/и т. д.

  • Вам также следует использовать обновления функционального состояния для правильного обновления предыдущего состояния вместо того, что закрывается в области обратного вызова.
  • В сопоставленных параметрах отсутствует ключ React. Я предлагаю добавить к параметрам свойство id, чтобы однозначно идентифицировать их, тем более что вы изменяете массив, добавляя и удаляя элементы. Это должно помочь избежать проблем с рендерингом React.
  • Существует также «несоответствие» именования реквизитов в Type2, где установщик назван так же, как и установщик компонента Type1, т. е. setType1Options вместо setType2Options. Здесь нет ошибки, но соглашение об именах может сбить с толку будущих читателей/сопровождающих вашего кода.

Приложение.jsx

import { useState } from "react";
import List from "./List";
import { nanoid } from "nanoid"; // <-- generate GUIDs

export default function App() {
  const [type1Options, setType1Options] = useState([
    {
      id: nanoid(), // <-- generate GUID
      name: "Name1",
      value: "Value1",
    },
    {
      id: nanoid(), // <-- generate GUID
      name: "Name2",
      value: "Value2",
    },
  ]);

  const [type2Options, setType2Options] = useState([
    {
      id: nanoid(), // <-- generate GUID
      name: "Name1",
      value: "Value1",
    },
    {
      id: nanoid(), // <-- generate GUID
      name: "Name2",
      value: "Value2",
    },
  ]);

  return (
    <div>
      <List
        type1Options = {type1Options}
        type2Options = {type2Options}
        setType1Options = {setType1Options}
        setType2Options = {setType2Options}
      />
    </div>
  );
}

utils.ts

import { nanoid } from "nanoid";

export const deleteItems = (list, idx) => {
  const temp = [...list];
  temp.splice(idx, 1);
  return temp;
};

export const copyItems = (list, idx) => {
  const newItem = {
    ...list[idx],
    id: nanoid(), // <-- generate new id for copied element
  };

  return [
    ...list.slice(0, idx + 1),
    newItem,
    ...list.slice(idx + 1)
  ];
};

Список.jsx

export default function List({
  type1Options,
  type2Options,
  setType1Options,
  setType2Options,
}) {
  return (
    <>
      <div>
        Category 1
        {type1Options.map((obj, index) => (
          <Type1
            key = {obj.id} // <-- use id as React key
            index = {index}
            obj = {obj}
            setType1Options = {setType1Options}
          />
        ))}
      </div>
      <br />
      <div>
        Category 2
        {type2Options.map((obj, index) => (
          <Type2
            key = {obj.id} // <-- use id as React key
            index = {index}
            obj = {obj}
            setType2Options = {setType2Options}
          />
        ))}
      </div>
    </>
  );
}

Тип1.jsx

export default function Type1({ index, obj, setType1Options }) {
  const copyHandler = () =>
    setType1Options((type1Options) => copyItems(type1Options, index));

  const deleteHandler = () =>
    setType1Options((type1Options) => deleteItems(type1Options, index));

  return (
    <div className = "box-container">
      <div className = "box-header">
        <h3>Index {index + 1}</h3>
        <div className = "box-buttons">
          <button onClick = {copyHandler}>Copy</button>
          <button onClick = {deleteHandler}>Delete</button>
        </div>
      </div>
      <div className = "box-content">
        {obj.name}: {obj.value}
      </div>
    </div>
  );
}

Тип2.jsx

export default function Type2({ index, obj, setType2Options }) {
  const copyHandler = () =>
    setType2Options((type2Options) => copyItems(type2Options, index));

  const deleteHandler = () =>
    setType2Options((type2Options) => deleteItems(type2Options, index));

  return (
    <div className = "box-container">
      <div className = "box-header">
        <h3>Index {index + 1}</h3>
        <div className = "box-buttons">
          <button onClick = {copyHandler}>Copy</button>
          <button onClick = {deleteHandler}>Delete</button>
        </div>
      </div>
      <div className = "box-content">
        {obj.name}: {obj.value}
      </div>
    </div>
  );
}

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