I have the following state:
[
[{ id: 2, valid: true, name: "b", visible: true }],
[{ id: 1, valid: true, name: "a", visible: false },
{ id: 3, valid: false, name: "c", visible: false }]
]
I use one Component to render each element of these arrays.
Users can edit the items, in this Component.
To achieve that I first shallow copy the Item and then update the attribute of the Item that was changed. See code:
export default function Item({item, updateItem}) {
function update(key, val){
let new_item = { ...item };
new_item[key] = val;
updateItem(new_item)
}
....
In the App Component i then update the actual state:
function updateItem(newItem){
setItems((sourceList) => {
let tempList = [...sourceList];
tempList.forEach((elements) => {
elements.forEach((item, idx) => {
if(item.id == newItem.id){
elements[idx] = newItem
}
})
});
return tempList;
})
}
Is this a valid way of doing it, or is this bad way? How would you update the State of this complex items array?
CodePudding user response:
One way you could do it is with a custom hook. I've used Typescript here, and I've thrown together a sandbox with an example component for you: https://codesandbox.io/s/flamboyant-chaum-zb2kle?file=/src/App.ts
type Item = {
id: number;
name: string;
valid: boolean;
visible: boolean;
};
export interface ItemHook {
items: Item[];
addItem: (item: Item) => void;
changeItem: (id: number, changes: Partial<Item>) => void;
}
export function useItems(): ItemHook {
const [items, setItems] = useState<Item[]>([]);
function addItem(item: Item) {
// append to the existing state.
setItems((items) => [...items, item]);
}
function changeItem(id: number, changes: Partial<Item>) {
// map the old list of items to a new list with the requisite changes.
setItems((items) =>
items.map((oldItem) => {
if (oldItem.id === id) {
// change the item with the associated id.
// its important ...changes comes after ...oldItem
return {
...oldItem,
...changes
};
} else return oldItem; //only change matching id.
})
);
}
return { items, addItem, changeItem };
}
export default function App() {
const [name, setName] = useState("");
const [changeId, setChangeId] = useState<number | undefined>();
const { items, addItem, changeItem } = useItems();
function handleClick() {
if (changeId !== undefined) {
// we are changing an existing item.
changeItem(changeId, { name });
} else {
// we are making a new item.
addItem({
id: items.reduce((id, item) => Math.max(id, item.id), 0) 1,
name,
visible: true,
valid: true
});
}
}
return (
<>
<label>
Change ID:
<input
type="number"
onChange={(e) => setChangeId(parseFloat(e.target.value))}
value={changeId}
/>
</label>
<label>
Name:
<input onChange={(e) => setName(e.target.value)} value={name} />
</label>
<button onClick={handleClick}>Add/Change</button>
<ul>
{items
.filter((item) => item.visible)
.map((item) => (
<li key={item.id}>
{item.id}:{item.name}
</li>
))}
</ul>
</>
);
}
Basically what I'm saying is our useItems hook exposes a list of the current items, a method to add an item, and a method to change an item. Inside addItem we just copy over the old items and add the new one. Inside changeItems we copy all of the items to the new state, and for the one where the id matches we also apply those changes.
The hook is nice because it exposes a minimum interface, its easily testable, and reusable.