У меня есть массив объектов следующего формата
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>
);
}
🤔 А знаете ли вы, что...
JavaScript позволяет создавать динамические и интерактивные веб-приложения.
Проблема заключалась в несоответствии 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>
);
}
Компонент 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
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>
);
}