As a fairly new addict to programming, I'm stuck with a simple app that lists items. Adding items, is OK. But as for deleting one item, I just can't figure out how to implement this function. Now on clicking an item the whole array of items is deleted. I have tried to put the deleteItem function in other files, but I seem to have mixed up the references.
ItemContext.js:
import React, { useState, createContext } from 'react';
import { v1 as uuidv1 } from 'uuid';
export const ItemContext = createContext();
export const ItemProvider = (props) => {
const [items, setItems] = useState([
{
name: "Red Beans",
amount: 23,
id: uuidv1()
},
{
name: "Nuts for Bunnies",
amount: 33,
id: uuidv1()
},
{
name: "Chopped Tomatoes",
amount: 2,
id: uuidv1()
}
]);
return (
<ItemContext.Provider value={[items, setItems]} >
{props.children}
</ItemContext.Provider>
);
}
AddItem.js:
import React, { useState, useContext } from 'react';
import { ItemContext } from './ItemContext';
const AddItem = () => {
const [name, setName] = useState('');
const [amount, setAmount] = useState('');
const [items, setItems] = useContext(ItemContext);
const updateName = (e) => {
setName(e.target.value)
}
const updateAmount = (e) => {
setAmount(e.target.value)
}
const addItem = (e) => {
e.preventDefault();
setItems(prevItems => [...prevItems, {name: name, amount: amount, key: items.id}])
}
return (
<form className="new-item" onSubmit={addItem}>
<input type="text" name="name" value={name} onChange={updateName} placeholder="Add a new item"/>
<input type="text" name="amount" value={amount} onChange={updateAmount} placeholder="Amount"/>
<button>Submit</button>
</form>
);
}
export default AddItem;
Item.js:
import React, { useState, useContext } from 'react';
import { ItemContext } from './ItemContext';
const Item = ({name, amount, /*deleteItem*/ }) => {
const [id, setId] = useState('');
const [items, setItems] = useContext(ItemContext);
const deleteItem = (id) => {
// const newItems = items.filter(item => items.id !== id);
// setItems(newItems);
const newItems = Object.assign([], ...items)
items.splice(id, 1);
setItems(newItems);
}
return (
<div>
<h3>{name}</h3>
<p>{amount}</p>
<button onClick={() => deleteItem(items.id)} className="delete-btn">Delete</button>
</div>
);
}
export default Item;
ItemsList.js:
import React, { useState, useContext } from 'react';
import Item from './Item';
import { ItemContext } from './ItemContext';
import { v1 as uuidv1 } from 'uuid';
const ItemsList = () => {
const [items, setItems] = useContext(ItemContext);
const [id, setId] = useState('');
const deleteItem = (id) => {
// const newItems = items.filter(item => items.id !== id);
// setItems(newItems);
const newItems = Object.assign([], ...items)
items.splice(id, 1);
setItems(newItems);
}
return (
<div className="items-list">
{items.map(item => (
<Item name={item.name} amount={item.amount} key={uuidv1()} deleteItem={deleteItem}/>
))}
</div>
);
}
export default ItemsList;
Initially you need to provide a valid initial value to the ItemContext
context you are creating. Its signature should match what the context value will be. I suggest using an object instead of an array.
export const ItemContext = createContext({
items: [],
addItem: () => {},
deleteItem: () => {}
});
Then define addItem
and deleteItem
handlers to include in the context value. You want to avoid exposing out the setItems
state function directly so ItemProvider
maintains control over the state invariant.
const initialState = [
{
name: "Red Beans",
amount: 23,
id: uuidv1()
},
{
name: "Nuts for Bunnies",
amount: 33,
id: uuidv1()
},
{
name: "Chopped Tomatoes",
amount: 2,
id: uuidv1()
}
];
const ItemProvider = (props) => {
const [items, setItems] = useState(initialState);
const addItem = (item) => setItems((items) => [...items, item]);
const deleteItem = (id) =>
setItems((items) => items.filter((item) => item.id !== id));
const value = {
items,
addItem,
deleteItem
};
return (
<ItemContext.Provider value={value}>
{props.children}
</ItemContext.Provider>
);
};
Then destructure the ItemContext
value where you are consuming it.
AddItem.js - Make sure to assign a new uuidV1 GUID for new items. I think this was your biggest issue, you were assigning undefined
to a key
property (key: items.id
) instead of creating a new GUID for the id
property.
import React, { useState, useContext } from 'react';
import { ItemContext } from './ItemContext';
const AddItem = () => {
const [name, setName] = useState("");
const [amount, setAmount] = useState("");
const { addItem } = useContext(ItemContext);
const updateName = (e) => {
setName(e.target.value);
};
const updateAmount = (e) => {
setAmount(e.target.value);
};
const submitHandler = (e) => {
e.preventDefault();
addItem({
name: name,
amount: amount,
id: uuidv1() // <-- new GUID here!!
});
};
return (
<form className="new-item" onSubmit={submitHandler}>
<input
type="text"
name="name"
value={name}
onChange={updateName}
placeholder="Add a new item"
/>
<input
type="text"
name="amount"
value={amount}
onChange={updateAmount}
placeholder="Amount"
/>
<button>Submit</button>
</form>
);
};
Item.js - Get deleteItem
from the context and ensure to pass item id
to component.
import React, { useState, useContext } from 'react';
import { ItemContext } from './ItemContext';
const Item = ({ name, amount, id }) => {
const { deleteItem } = useContext(ItemContext);
return (
<div>
<h3>{name}</h3>
<p>{amount}</p>
<button onClick={() => deleteItem(id)} className="delete-btn">
Delete
</button>
</div>
);
};
ItemsList.js - Set the React key to the current item id
and also pass the id
as a prop to `Item.
import React, { useState, useContext } from 'react';
import Item from './Item';
import { ItemContext } from './ItemContext';
const ItemsList = () => {
const { items } = useContext(ItemContext);
return (
<div className="items-list">
{items.map((item) => (
<Item
key={item.id} // <-- Static React key to item
id={item.id} // <-- Pass id as prop for delete handler to use
name={item.name}
amount={item.amount}
/>
))}
</div>
);
};