How can I delete an item inside a nested array with Hooks? - reactjs

I am trying to remove a single item from state inside a nested array, but i am really struggling to understand how.
My data looks as follows, and I'm trying to remove one of the 'variants' objects on click.
const MYDATA = {
id: '0001',
title: 'A good title',
items: [
{
itemid: 0,
title: 'Cheddar',
variants: [
{ id: '062518', grams: 200, price: 3.00},
{ id: '071928', grams: 400, price: 5.50},
]
},
{
itemid: 1,
title: 'Edam',
variants: [
{ id: '183038', grams: 220, price: 2.50},
{ id: '194846', grams: 460, price: 4.99},
]
},
{
itemid: 2,
title: 'Red Leicester',
variants: [
{ id: '293834', grams: 420, price: 4.25},
{ id: '293837', grams: 660, price: 5.99},
]
}
]
}
Against each variant is a button which calls a remove function, which (should) remove the deleted item and update the state. However, this is not happening and I'm not sure what I am doing wrong.
const [myCheeses, setMyCheeses] = useState(MYDATA);
const removeMyCheese = (variantID, itemindx) => {
console.log(variantID);
setMyCheeses((prev) => {
const items = myCheeses.items[itemindx].variants.filter(
(variant) => variant.id !== variantID
);
console.log(items, itemindx);
return {
...myCheeses.items[itemindx].variants,
items
};
});
};
An example of the issue I'm facing can be seen here
https://codesandbox.io/s/funny-dan-c84cr?file=/src/App.js
Any help would be truly appreciated.

The issue is that, setMyCheeses function not returning the previous state including your change(removal)
Try one of these functions;
1st way
const removeMyCheese = (variantID, itemindx) => {
setMyCheeses((prev) => {
const items = myCheeses.items[itemindx].variants.filter(
(variant) => variant.id !== variantID
);
const newState = prev;
newState.items[itemindx].variants = items;
return {...newState};
});
};
https://codesandbox.io/s/bold-worker-b12x1?file=/src/App.js
2nd way
const removeMyCheese = (variantID, itemindx) => {
setMyCheeses((prev) => {
const items = myCheeses.items.map((item, index) => {
if (itemindx === index) {
return {
...item,
variants: item.variants.filter(
(variant) => variant.id !== variantID
)
};
} else {
return item;
}
});
return { ...prev, items: items };
});
};
https://codesandbox.io/s/sharp-forest-qhhwd

try this function, it's work for me :
const removeMyCheese = (variantID, itemindx) => {
//console.log(variantID);
const newMyCheeses = myCheeses;
const newItems = newMyCheeses.items.map((item) => {
return {
...item,
variants: item.variants.filter((variant) => variant.id !== variantID)
};
});
setMyCheeses({ ...newMyCheeses, items: newItems });
};
https://codesandbox.io/s/jolly-greider-fck6p?file=/src/App.js
Or, you can do somthing like this if you don't like to use the map function :
const removeMyCheese = (variantID, itemindx) => {
//console.log(variantID);
const newMyCheeses = myCheeses;
const newVariants = newMyCheeses.items[itemindx].variants.filter(
(variant) => variant.id !== variantID
);
newMyCheeses.items[itemindx].variants = newVariants;
setMyCheeses({ ...newMyCheeses });
};

Related

REACT- Displaying and filtering specific data

I want to display by default only data where the status are Pending and Not started. For now, all data are displayed in my Table with
these status: Good,Pending, Not started (see the picture).
But I also want to have the possibility to see the Good status either by creating next to the Apply button a toggle switch : Show good menus, ( I've made a function Toggle.jsx), which will offer the possibility to see all status included Good.
I really don't know how to do that, here what I have now :
export default function MenuDisplay() {
const { menuId } = useParams();
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus] = useState([]);
useEffect(() => {
axios.post(url,{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Status",
accessor: (row) => row.status
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
return (
<div>
<button type="button" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
<Table
data={matchData}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
{
"menuId": 1,
"_id": "123ml66",
"name": "Pea Soup",
"description": "Creamy pea soup topped with melted cheese and sourdough croutons.",
"dishes": [
{
"meat": "N/A",
"vegetables": "pea"
}
],
"taste": "Good",
"comments": "3/4",
"price": "Low",
"availability": 0,
"trust": 1,
"status": "Pending",
"apply": 1
},
//...other data
]
Here my CodeSandbox
Here a picture to get the idea:
Here's the second solution I proposed in the comment:
// Setting up toggle button state
const [showGood, setShowGood] = useState(false);
const [menus, setMenus] = useState([]);
// Simulate fetch data from API
useEffect(() => {
async function fetchData() {
// After fetching data with axios or fetch api
// We process the data
const goodMenus = dataFromAPI.filter((i) => i.taste === "Good");
const restOfMenus = dataFromAPI.filter((i) => i.taste !== "Good");
// Combine two arrays into one using spread operator
// Put the good ones to the front of the array
setMenus([...goodMenus, ...restOfMenus]);
}
fetchData();
}, []);
return (
<div>
// Create a checkbox (you can change it to a toggle button)
<input type="checkbox" onChange={() => setShowGood(!showGood)} />
// Conditionally pass in menu data based on the value of toggle button "showGood"
<Table
data={showGood ? menus : menus.filter((i) => i.taste !== "Good")}
/>
</div>
);
On ternary operator and filter function:
showGood ? menus : menus.filter((i) => i.taste !== "Good")
If button is checked, then showGood's value is true, and all data is passed down to the table, but the good ones will be displayed first, since we have processed it right after the data is fetched, otherwise, the menus that doesn't have good status is shown to the UI.
See sandbox for the simple demo.

Change variable in array of objects in state React

What I wanted to do is whenever I click button to change variable in states array of objects. I did it this way, but is there any easier way to do it?
completeItem = (id) => {
const item = this.state.items.filter(el =>{
return el.id === id;
});
item[0].isCompleted = true;
const state = this.state.items;
const arr = this.state.items.filter(el =>{
if(el.id === id){
state[id-1] = item[0];
}
return state;
});
this.setState({
items:[...arr],
});
}
Try this solution...
completeItem = (id) => {
const items = this.state.items.filter(el =>{
if(el.id === id){
el.isCompleted = true;
}
return el;
});
this.setState({
items : [...items],
});
}
If the solution is helpful please let me know!
May compromise a little performance, but it is more readable.
completeItem = (id) => {
const newList = [...this.state.items]
const index = newList.findIndex(el => el.id === id);
newList[index].isCompleted = true
this.setState({ items: newList });
}
Here is how you can do it:
const items = [{
id: 1,
name: "one",
isCompleted: false
},
{
id: 2,
name: "two",
isCompleted: false
},
{
id: 3,
name: "three",
isCompleted: false
},
{
id: 4,
name: "four",
isCompleted: false
}
]
completeItem = (id) => {
const result = items.map(e => {
if (e.id === id) {
return { ...e,
isCompleted: true
}
} else {
return e;
}
});
console.log(result)
}
completeItem(2)

Change multiple key values and setState in React

How to change value all of keys "isVisible"?
state = {
players: [
{
id: 0,
name: 'Gabriel',
isVisible: false,
},
{
id: 1,
name: 'John',
isVisible: false,
},
]
I have tried:
handleclick = () => {
let players = this.state.players
players = players.map(player => player.set("isVisible", true))
this.setState({
players
})
}
It throw error because president.set is not a function. Any ideas?
try this:
handleclick = () => {
let players = this.state.players
players = players.map(player => {player.isVisible = true; return player;}))
this.setState({
players
})
}

How to map trough array of object and toggle boolean property selected

I have state in React functional component. It is and array of objects. Every object in that collection has property "selected", which is a boolean. That array looks like this:
const [filterOptions, setFilterOptions] = useState([
{
title: 'highest',
selected: true,
},
{
title: 'lowest',
selected: false,
},
]);
After handleFilter func is executed I need to set state so this array has same title properties but reverse (toggle) selected properties.
This is handleFilter func in which I need to toggle every selected property of array objects:
const handleFilter = () => {
setFilterOptions();
};
function App() {
const [filterOptions, setFilterOptions] = useState([
{
title: 'highest',
selected: true,
},
{
title: 'lowest',
selected: false,
},
]);
const handleFilter = (e) => {
let newArr = [...filterOptions];
let value = e.target.value;
if (value === "lowest") {
newArr[0].selected = true;
newArr[1].selected = false;
} else if (value === "highest") {
newArr[0].selected = false;
newArr[1].selected = true;
}
setFilterOptions(newArr)
};
return (
<div>
<select onChange={handleFilter}>
<option value="lowest">a</option>
<option value="highest">b</option>
</select>
{console.log((filterOptions))}
</div>
);
}
please check hope it will work
var arryObj =[
{
title: 'highest',
selected: true,
},
{
title: 'lowest',
selected: false,
},
]
const handleFilter = (index,value) => {
arryObj[index].selected = value
};
handleFilter(0,false)
console.log(arryObj)
handleFilter(1,true)
console.log(arryObj)
You can pass a function into setFilterOptions to change the state based on the previous state.
const handleFilter = () => {
setFilterOptions(prevState =>
prevState.map(obj => ({...obj, selected: !obj.selected}))
);
};

Update item in state onClick ReactJS

So, I have class Comopnent :
state = {
tokens: [
{
name: "first",
value: 3
},
{
name: "second",
value: 2
},
{
name: "third",
value: 4
}
]
}
handleClick = (name, id) => {
const newState = this.state.tokens.map((token => {
console.log(token.name)
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div value={t.name} onClick={() => this.handleClick(t.name, t.value)}>
<img src=""/>
</div>
)
})
What i need to do - after click - to subtract 1 from value clicked token.
So - ex. after click on "First" token i want his value equal 2.
So far I've done just as much as the above.
I do not know how to go about it, i am new in ReactJS, so thanks for help in advance!
You'll have to find in your state in tokens array the object which has the same name as the argument passed in the onclick handler. Then you will have to change it's value - decrement it (value--) but you have to be aware that you can't mutate the state.
handleClick = name => () => {
const { tokens } = this.state;
const clickedToken = tokens.find(token => token.name === name);
clickedToken.value--;
const clickedTokenIndex = tokens.indexOf(clickedToken);
const newTokens = [
...tokens.slice(0, clickedTokenIndex),
clickedToken,
...tokens.slice(clickedTokenIndex + 1)
];
this.setState({ tokens: newTokens });
};
Codesandbox link: https://codesandbox.io/s/92yz34x97w
First, some things are wrong with your code.
1- You have an array of tokens, then you're mapping the list, but you don't have a key to index, this will cause weird behaviors, I improve your tokens list with keys now.
2.- You can handle the click and change the state of the tokens list, this will trigger a reload of the component.
state = {
tokens: [
{
name: "first",
value: 3,
id: 1
},
{
name: "second",
value: 2,
id: 2
},
{
name: "third",
value: 4,
id: 3
}
]
}
handleClick = (name, id) => {
const { tokens} = this.state;
const newState = tokens.map((token => {
if(token.id === id) {
token.value--;
}
return token;
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div key={t.key} value={t.name} onClick={() => this.handleClick(t.name, t.value, t.key)}>
<img src=""/>
</div>
)
})

Resources