Вопрос о правильности написания кода с точки зрения архитектуры React?
Есть многомерный массив. Я делаю маппинг по нему чтобы отрисовать таблицу. Данные получил, но мне также нужно отрисовать body таблицы данными из массива "data". В "data" лежит объект вида {title:string,number:number}, мне нужно добавить сюда свойство id чтобы сделать сортировку по этому полю и вот вопрос, на каком уровне правильнее мне изменить этот объект, соблюдая принцип иммутабельности и правильности написания React приложений?
export const TableBlock: FC<IParentData> = ({ title, subTitle, dateStart, dateEnd, data }) => {
const [tableVisible, setTableVisible] = useState(false);
return (
<div className='table-wrapper'>
<div className="table-block" >
<div className='table-block__left'>
<h5 className='table-block__title'>{title}</h5>
<h6 className='table-block__subtitle'>{subTitle}</h6>
</div>
<div className='table-block__right'>
<p className='table-block__date'>{dateStart} - {dateEnd}</p>
</div>
</div>
{tableVisible && <Table data={data} />}
</div>
)
}
export const Table: FC<IProps> = ({ data }) => {
return (
<table className='table'>
<thead className='thead'>
<tr className='trow'>
<th># <span className='icon'> <img src={ArrowIcon} alt="" /></span> </th>
<th>Title </th>
<th>Number <span className='icon'> <img src={ArrowIcon} alt="" /></span> </th>
</tr>
</thead>
{data.map((el,index) =>{
return (
<tbody key={el.title} className='tbody'>
<tr className='trow'>
<td>{index}</td>
<td>{el.title}</td>
<td>{el.number}</td>
</tr>
</tbody>
)
})}
</table>
)
Ответы (1 шт):
На мой взгляд, наиболее удобно мутировать объекты в массиве через словарь Map. Посмотрите пример хука и его использования
Хук
useListEditorпозволяет рендерить список объектов с возможностью их изменения по динамически генерирующемусяid
import React, { useState, useMemo, useEffect, useRef, Fragment } from "react";
type RowId = number;
export const useListEditor = <Data extends any = undefined>(renderItem: (id: RowId, item: Data) => React.ReactElement, {
initialValue = [],
onChange,
}: {
initialValue?: Data[],
onChange?: (items: Data[]) => void;
}) => {
const [items, setItems] = useState(new Map<RowId, Data>(initialValue.map((item, idx) => [idx, item])));
const initialChange = useRef(true);
const createId = () => Math.max(...items.keys(), 0) + 1;
const onAddItem = (data: Data) => {
const id = createId();
items.set(id, data);
setItems(new Map(items));
};
const onRemoveItem = (id: RowId) => {
items.delete(id);
setItems(new Map(items));
};
const onUpdateItem = (id: RowId, data: Data) => {
items.set(id, data);
setItems(new Map(items));
};
const itemList = useMemo(() => [...items.values()], [items]);
useEffect(() => {
if (initialChange.current) {
initialChange.current = false;
return;
}
onChange && onChange(itemList);
}, [itemList]);
const render = () => (
<>
{[...items.entries()].map(([id, item], idx) => (
<Fragment key={idx}>
{renderItem(id, item)}
</Fragment>
))}
</>
);
return {
onAddItem,
onUpdateItem,
onRemoveItem,
items: itemList,
render,
};
};
export default useListEditor;
Пример использования, можно запустить на codesandbox
import { useListEditor } from "react-declarative";
export const EditableList = () => {
const { onRemoveItem, onUpdateItem, onAddItem, render } = useListEditor(
(id, item) => (
<>
<span>{`Label: ${item.label}`}</span>
<span>{`Value: ${item.value}`}</span>
<button
onClick={() =>
onUpdateItem(id, {
label: prompt("label", item.label),
value: prompt("value", item.value)
})
}
>
update
</button>
<button onClick={() => onRemoveItem(id)}>remove</button>
</>
),
{
initialValue: [
{
label: "foo",
value: "bar"
}
],
onChange: (items) => console.log(items)
}
);
return (
<>
<button
style={{
marginBottom: 5
}}
onClick={() =>
onAddItem({
label: "fiz",
value: "baz"
})
}
>
Add
</button>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr auto auto",
gridColumnGap: 5,
gridRowGap: 5,
maxWidth: 300
}}
>
{render()}
</div>
</>
);
};
export default EditableList;
