I have a problem with the localStorage in my application. When I add items to a list of "favorites" they are stored without any problem in the localStorage, they can even be deleted by clicking them again.
But when I refresh the page, my application doesn't read that these items are in the favorites list and therefore doesn't mark them. Also, when I add a new item to the favorites list it causes it to delete everything from localStorage and start over.
Here's a gif of the localStorage view
Here's the code:
import React, { useState, useEffect } from 'react';
import SearchBar from '../../SearchBar/SearchBar.js';
import FiltersBox from '../FiltersBox/FiltersBox.js';
import { getItems } from '../../../Database/Database.js';
import './ItemsContainer.css';
function ItemsContainer() {
const [items, setItems] = useState([]);
const [search, setSearch] = useState('');
const [favoriteItems, setFavoriteItems] = useState([]);
let localItems = localStorage.getItem('Favorite Items');
const [sortPrice, setSortPrice] = useState('');
const [filterCategory, setFilterCategory] = useState('');
const addItemToFavorites = item => {
let existentItem = favoriteItems.find(favItem => favItem.id === item.id);
if (existentItem) {
let filterTheExistentItem = favoriteItems.filter(
favItem => item.title !== favItem.title
);
setFavoriteItems(filterTheExistentItem);
let stringItems = JSON.stringify(filterTheExistentItem);
localStorage.setItem('Favorite Items', stringItems);
} else {
setFavoriteItems([...favoriteItems, item]);
let stringItems = JSON.stringify([...favoriteItems, item]);
localStorage.setItem('Favorite Items', stringItems);
}
};
const filteredItemsList = () => {
let newItemList = [];
newItemList = items.filter(item => {
if (filterCategory !== '' && filterCategory !== 'none') {
return item.category === filterCategory;
} else {
return item;
}
});
if (sortPrice === 'ascending') {
return newItemList.sort((a, b) => (a.price > b.price ? 1 : -1));
} else if (sortPrice === 'descending') {
return newItemList.sort((a, b) => (b.price > a.price ? 1 : -1));
} else {
return newItemList;
}
};
function onSortSelected(sortValue) {
setSortPrice(sortValue);
}
function onCategorySelected(categoryValue) {
setFilterCategory(categoryValue);
}
useEffect(() => {
getItems().then(res => setItems(res));
}, []);
useEffect(() => {
let xd = JSON.parse(localItems);
console.log(xd);
}, [localItems]);
return (
<div>
<SearchBar setSearch={setSearch} />
<FiltersBox
items={items}
setItems={setItems}
onSortSelected={onSortSelected}
onCategorySelected={onCategorySelected}
/>
<div>
{filteredItemsList()
.filter(item =>
search.toLowerCase() === ''
? item
: item.title.toLowerCase().includes(search)
)
.map(item => (
<div key={item.id}>
<div>{item.title}</div>
<button
className={favoriteItems.includes(item) ? 'si' : 'no'}
onClick={() => addItemToFavorites(item)}>
Add to favorites
</button>
</div>
))}
</div>
</div>
);
}
export default ItemsContainer;
And here I leave a GIF with a continuous console.log of the localStorage:
I tried everyrhing, and I don't know what is happening.
You're retrieving your items in localItems and... you do nothing with this variable. You should initialize your state favoritesItems with your local storage
const getItemsFromLocalStorage = () => {
const items = localStorage.getItem('Favorite Items');
return items ? JSON.parse(items) : [];
}
const [favoriteItems, setFavoriteItems] = useState(getItemsFromLocalStorage())
This is where the culprit is:
const [favoriteItems, setFavoriteItems] = useState([]);
let localItems = localStorage.getItem('Favorite Items');
You load localStorage into localItems, but you expect it to be in favoriteItems, where you have never assigned it. You would need to specify the item of localStorage as the initial state, like:
let localItems = localStorage.getItem('Favorite Items');
const [favoriteItems, setFavoriteItems] = useState(localItems ? localItems : []);
Related
i am trying to query data using useEffect add those data to state and render them but nothing comes up unless a state in the app changes. this is what i have done so far Please help, Thanks in Advance.
useEffect
// fetchCampaigns
(async () => {
dispatch(showTopLoader());
try {
const res = await getAgentCampaigns(authToken, "accepted");
setCampaigns(res.data.campaigns);
let leads: any[] = [];
const fetchCampaignLeads = async (id: string) => {
try {
const res = await getCampaignLeads(authToken, id);
return res.data.campaignLeads;
} catch (error) {}
};
// loop through campaigns and get leads
let resS: any[] = [];
campaigns.forEach((campaign: any, i: number) => {
const id = campaign?.Campaign?.id;
fetchCampaignLeads(id)
.then((leadsRes) => {
leads.push(leadsRes[i]);
if (id === leadsRes[i]?.campaignId)
return resS.push({
...campaign,
leads: leadsRes,
});
return (resS = campaigns);
})
.catch(() => {})
.finally(() => {
console.log(resS);
setCampaigns(resS);
});
});
} catch (error) {
} finally {
dispatch(hideTopLoader());
}
})();
}, []);
whole component
import { useDispatch, useSelector } from "react-redux";
import _ from "lodash";
import styles from "../../../styles/CreateLeads.module.css";
import {
getAgentCampaigns,
getCampaignLeads,
} from "../../../utils/requests/campaign";
import {
hideTopLoader,
showTopLoader,
} from "../../../store/actions/TopLoader/topLoaderActions";
import CampaignSection from "./CampaignSection";
import Empty from "../Empty/Empty";
import SectionHeader from "../SectionHeader/SectionHeader";
import SearchBar from "../SearchBar/SearchBar";
import { RootState } from "../../../store/store";
const CreateLeadsCardsWrapper: React.FC = () => {
const authToken = useSelector(
(store: any) => store.authenticationReducer.authToken
);
const [stateCampaigns, setStateCampaigns] = React.useState<any[]>([]);
const [showCampaigns, setShowCampaigns] = React.useState<boolean>(false);
const [campaigns, setCampaigns] = React.useState<any[]>(stateCampaigns);
const [filter, setFilter] = React.useState<string>("");
const dispatch = useDispatch();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// Reset filter
setFilter("");
let campaignSearch = e.target.value.trim();
if (campaignSearch.length === 0) {
return;
}
let campaignSearchLower = campaignSearch.toLowerCase();
let campaignSearchUpper = campaignSearch.toUpperCase();
let campaignSearchSentence =
campaignSearch.charAt(0).toUpperCase() + campaignSearch.slice(1);
let results = stateCampaigns.filter(
({ leads }: { leads: any[] }, i) =>
leads &&
leads?.some(
(lead: any) =>
lead.firstName.includes(campaignSearch) ||
lead.firstName.includes(campaignSearchLower) ||
lead.firstName.includes(campaignSearchUpper) ||
lead.firstName.includes(campaignSearchSentence) ||
lead.lastName.includes(campaignSearch) ||
lead.lastName.includes(campaignSearchLower) ||
lead.lastName.includes(campaignSearchUpper) ||
lead.lastName.includes(campaignSearchSentence) ||
lead.email.includes(campaignSearch) ||
lead.email.includes(campaignSearchLower) ||
lead.email.includes(campaignSearchUpper) ||
lead.email.includes(campaignSearchSentence) ||
lead.phoneNo.includes(campaignSearch) ||
lead.phoneNo.includes(campaignSearchLower) ||
lead.phoneNo.includes(campaignSearchUpper) ||
lead.phoneNo.includes(campaignSearchSentence)
)
);
setCampaigns(results);
};
React.useEffect(() => {
// fetchCampaigns
(async () => {
dispatch(showTopLoader());
try {
const res = await getAgentCampaigns(authToken, "accepted");
setCampaigns(res.data.campaigns);
let leads: any[] = [];
const fetchCampaignLeads = async (id: string) => {
try {
const res = await getCampaignLeads(authToken, id);
return res.data.campaignLeads;
} catch (error) {}
};
// loop through campaigns and get leads
let resS: any[] = [];
campaigns.forEach((campaign: any, i: number) => {
const id = campaign?.Campaign?.id;
fetchCampaignLeads(id)
.then((leadsRes) => {
leads.push(leadsRes[i]);
if (id === leadsRes[i]?.campaignId)
return resS.push({
...campaign,
leads: leadsRes,
});
return (resS = campaigns);
})
.catch(() => {})
.finally(() => {
console.log(resS);
setCampaigns(resS);
});
});
} catch (error) {
} finally {
dispatch(hideTopLoader());
}
})();
}, []);
React.useEffect(() => {
setCampaigns(stateCampaigns);
campaigns.length > 0 && setShowCampaigns(true);
console.log(showCampaigns);
dispatch(hideTopLoader());
}, []);
return (
<div className={styles.wrappers}>
{/* Multi step form select a campaign first then fill info on the next step */}
<SectionHeader text="Campaign Leads" />
{showCampaigns && stateCampaigns.length === 0 && (
<>
<Empty description="No agents yet" />
<p>Join a campaign.</p>
</>
)}
{showCampaigns && stateCampaigns.length > 0 && (
<>
<p className="text-grey-500">Create Leads for Campaigns.</p>
<section className={styles.container}>
<SearchBar
placeholder="Find Campaign Leads"
onChange={handleChange}
/>
{campaigns.map((item: any) => (
<CampaignSection
key={item?.Campaign?.id}
id={item?.Campaign?.id}
name={item?.Campaign?.name}
imageUrl={item?.Campaign?.iconUrl}
campaignType={item?.Campaign?.type}
productType={item?.Campaign?.Products[0]?.type}
/>
))}
</section>
</>
)}
</div>
);
};
export default CreateLeadsCardsWrapper;
there are two things wrong in yoour code :
1- you should not have two useeffects with the same dependencies in your case: [] you have to merge those useeffects into one or change the second one's dependencies
2- doing async code in useeffect can be problematic sometimes. it is better to create an async function which does the query to the backend and sets the state and the call the function in your useeffect like below :
const getData = async()=>{
// do some queries and set the state
}
React.useeffect(()=>{
getData()
},[])
I am making an app which should load content from api and immediately display components based on that data. Firstly, when it fetches the data, it should dispatch it to redux store and then with useSelector should trigger the change and pass the value as a prop to children component. Problem is that it does dispatch and select it with selector (there are console logs in code to confirm) but in doesn't pass it to child via props. I know that I can use useSelector in child but I am now intrested why doesn't it work this way.
Component 1
const Menu = () => {
console.log("reload");
const food = useSelector((state) => state.foodList.food);
console.log(food);
const [foodList, setFoodList] = useState(food);
// console.log(foodList);
const dispatch = useDispatch();
const page = useSelector((state) => state.ui.page);
useEffect(() => {
const getFoodList = async () => {
const response = await fetch(
"https://senbonzakura-food-default-rtdb.firebaseio.com/food.json"
);
if (!response.ok) throw new Error("Something went wrong!");
const data = await response.json();
const foodList = data[Object.keys(data)[0]];
console.log("---");
console.log(foodList);
console.log("---");
dispatch(foodSliceActions.updateFoodList(foodList));
};
try {
getFoodList();
// dispatch(foodSliceActions.updateFoodList(foodList));
} catch (err) {
console.log(err);
}
}, []);
// const navigate = useNavigate();
// const queryPrams = new URLSearchParams(location.search);
// const sort = queryPrams.get("sort");
return (
<main className={classes["menu-main"]}>
<section
className={`${classes["menu__section"]} ${classes["menu__left-side"]}`}
>
<div className={classes["menu__label"]}>
<h1>MENU</h1>
<h1>{page + 1}</h1>
</div>
<MenuList foodList={foodList ? foodList : []} page={page} />
</section>
<section
className={`${classes["menu__section"]} ${classes["menu__right-side"]}`}
>
<Outlet />
</section>
</main>
);
};
export default Menu;
Child component
const formatArray = (array) => {
const pages = Math.ceil(array.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
helpArr.push(array[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
return arr;
};
const MenuList = (props) => {
const page = props.page;
const dispatch = useDispatch();
const DUMMY_FOOD = props.foodList;
console.log(DUMMY_FOOD);
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const [foodList, setFoodList] = useState([]);
useEffect(() => {
setFoodList(formatArray(DUMMY_FOOD));
}, []);
const queryPrams = new URLSearchParams(location.search);
const sort = queryPrams.get("sort");
const onNextPageHandler = () => {
dispatch(uiSliceActions.updatePage("forward"));
};
const onPreviousPageHandler = () => {
dispatch(uiSliceActions.updatePage("backward"));
};
const onSortPageHandler = () => {
navigate(`/menu/${params.foodId}/?sort=${sort === "asc" ? "desc" : "asc"}`);
sort === "asc"
? (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => a.foodPrice - b.foodPrice))
: (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => b.foodPrice - a.foodPrice));
};
return (
<Fragment>
<div className={classes["menu-list"]}>
{foodList[page]
? foodList[page].map((foodObj) => (
<MenuItem key={foodObj.id} foodObj={foodObj} />
))
: ""}
</div>
<div className={classes["menu-list__buttons"]}>
{page >= 1 && (
<Button type="button" onClick={onPreviousPageHandler}>
Page {page}
</Button>
)}
<Button type="button" onClick={onSortPageHandler}>
{sort === "asc" ? `Descending` : `Ascending`}
</Button>
<Button type="button" onClick={onNextPageHandler}>
Page {page + 2}
</Button>
</div>
</Fragment>
);
};
export default MenuList;
food-slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = { food: [] };
const foodSlice = createSlice({
name: "foodList",
initialState,
reducers: {
updateFoodList(state, action) {
// console.log(action.payload);
state.food = action.payload;
},
},
});
const foodSliceActions = foodSlice.actions;
export const foodSliceReducer = foodSlice.reducer;
export default foodSliceActions;
index (store)
import { configureStore } from "#reduxjs/toolkit";
import { uiSliceReducer } from "./ui-slice";
import { cartSliceReducers } from "./cart-slice";
import { foodSliceReducer } from "./food-slice";
const store = configureStore({
reducer: {
ui: uiSliceReducer,
cart: cartSliceReducers,
foodList: foodSliceReducer,
},
});
export default store;
Thank you.
You wrote the following code in component1
const food = useSelector((state) => state.foodList.food);
const [foodList, setFoodList] = useState(food);
Here, you set food as initial state value of foodList state, so foodList is updated as food only when the component1 loads initially (componentDidMount lifecycle), instead of updating whenever food is updated.
If you wants to update foodList whenever food is updated, you need to set it in useEffect hook.
useEffect(() => { setFoodList(food) }, [food]);
Btw, you don't need to pass food as props to the child component.
You can use food directly in child component.
I updated your code.
component1
const Menu = () => {
const dispatch = useDispatch();
const page = useSelector((state) => state.ui.page);
useEffect(() => {
const getFoodList = async () => {
const response = await fetch(
"https://senbonzakura-food-default-rtdb.firebaseio.com/food.json"
);
if (!response.ok) throw new Error("Something went wrong!");
const data = await response.json();
const foodList = data[Object.keys(data)[0]];
dispatch(foodSliceActions.updateFoodList(foodList));
};
try {
getFoodList();
} catch (err) {
console.log(err);
}
}, []);
return (
<main className={classes["menu-main"]}>
<section
className={`${classes["menu__section"]} ${classes["menu__left-side"]}`}
>
<div className={classes["menu__label"]}>
<h1>MENU</h1>
<h1>{page + 1}</h1>
</div>
<MenuList page={page} />
</section>
<section
className={`${classes["menu__section"]} ${classes["menu__right-side"]}`}
>
<Outlet />
</section>
</main>
);
};
export default Menu;
child component
const formatArray = (array) => {
const pages = Math.ceil(array.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
helpArr.push(array[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
return arr;
};
const MenuList = (props) => {
const page = props.page;
const dispatch = useDispatch();
const DUMMY_FOOD = useSelector((state) => state.foodList.food);
const [foodList, setFoodList] = useState([]);
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const queryPrams = new URLSearchParams(location.search);
const sort = queryPrams.get("sort");
useEffect(() => {
setFoodList(DUMMY_FOOD);
}, [DUMMY_FOOD]);
const onNextPageHandler = () => {
dispatch(uiSliceActions.updatePage("forward"));
};
const onPreviousPageHandler = () => {
dispatch(uiSliceActions.updatePage("backward"));
};
const onSortPageHandler = () => {
navigate(`/menu/${params.foodId}/?sort=${sort === "asc" ? "desc" : "asc"}`);
sort === "asc"
? (setFoodList(foodList.sort((a, b) => a.foodPrice - b.foodPrice)))
: (setFoodList(foodList.sort((a, b) => b.foodPrice - a.foodPrice)));
};
return (
<Fragment>
<div className={classes["menu-list"]}>
{foodList[page]
? foodList[page].map((foodObj) => (
<MenuItem key={foodObj.id} foodObj={foodObj} />
))
: ""}
</div>
<div className={classes["menu-list__buttons"]}>
{page >= 1 && (
<Button type="button" onClick={onPreviousPageHandler}>
Page {page}
</Button>
)}
<Button type="button" onClick={onSortPageHandler}>
{sort === "asc" ? `Descending` : `Ascending`}
</Button>
<Button type="button" onClick={onNextPageHandler}>
Page {page + 2}
</Button>
</div>
</Fragment>
);
};
export default MenuList;
react
I wish clicking a button would reduce an item, but clicking a button would do nothing ... please help me...
const [cars, setCars] = useCars([])
const handleDelete = id => {
const remaining = cars.filter(car => car._id !== id);
setCars(' ');
}
please check this i have written sample code
import React, { useState } from 'react';
export default function App() {
const [lists, setLists] = useState([]);
const remove = (index) => {
// REMOVING THE RECORD BASED ON THE PREVIOUS STATE VALUE - ON THE GIVEN INDEX
setLists((v) => {
const a = [...v];
a.splice(index, 1);
return [...a];
});
};
// FOR ADDING SAMPLE DATA
const addList = () => {
setLists((v) => [...v, { name: 'List data ' + lists.length }]);
};
return (
<div>
<button onClick={addList}>Add List</button>
<ul>
{lists.map((e, k) => (
<li key={k}>
{e.name} <button onClick={(k) => remove(k)}>Remove</button>{' '}
</li>
))}
</ul>
</div>
);
}
I have a set of card objects that I map over.
When I click on a card it adds the selected class which in turn gives it a border to show the user it is selected, it also adds the id of the card to the selectedCards useState array.
WHAT I WANT TO HAPPEN:
Each card object has a creditAvailable key state which is equal to a figure.
On selection (click) of the card, in addition to selecting the card I would also like to add up the creditAvailable and display it on the screen. and when I unselect the card I would like the figure to go down.
WHAT I HAVE TRIED:
I thought it would be as simple as calling the function to add up the credit inside the first function which selects the card, however when console logging inside the first function I see that the state has not yet updated. (scope).
I then tried to call the function outside of the first function but it gave me an infinite loop. Here is my code.
Any ideas? Thanks
const [cards, setCards] = useState([]);
const [selectedCards, setSelectedCards] = useState([]);
const [total, setTotal] = useState();
const handleSelectCard = (id) => {
if (selectedCards.includes(id)) {
const filteredIds = selectedCards.filter((c) => c !== id);
setSelectedCards([...filteredIds]);
} else {
setSelectedCards([...selectedCards, id]);
}
// addUpCreditAvailable(); // nothing happens
console.log(selectedCards); // []
};
console.log(selectedCards) // [1] for example. This is in the global scope
const addUpCreditAvailable = () => {
console.log("inside add up credit");
const chosenCards = selectedCards.map((id) => {
const foundCard = allCards.find((card) => {
return card.id === id;
});
return foundCard;
});
const result = chosenCards.reduce((acc, card) => {
return acc + card.creditAvailable;
}, 0);
setTotal(result);
return result;
};
return (
<div className="Container">
<UserInputForm submitData={handleSubmitData} />
<h1> Cards available to you displayed are below!</h1>
{cards.map(
({
id,
name,
number,
apr,
balanceTransfer,
purchaseDuration,
creditAvailable,
expiry,
}) => (
<CreditCard
key={id}
name={name}
number={number}
apr={apr}
balanceTransferDuration={balanceTransfer}
purchaseOfferDuration={purchaseDuration}
creditAvailable={creditAvailable}
expiry={expiry}
onClickCard={() => handleSelectCard(id)}
selected={selectedCards.includes(id)}
/>
)
)}
<span> £{total}</span>
)}
I figured it out with the help from above. As Wilms said i had to return the result of the handleSelectCard function and return the result of the addUpCredit function. Then I called the addUpCreditAvailable with the selectedCards state and stored the result in a variable which i then displayed in my render method.
const [cards, setCards] = useState([]);
const [selectedCards, setSelectedCards] = useState([]);
const handleSelectCard = (id) => {
if (selectedCards.includes(id)) {
const filteredIds = selectedCards.filter((c) => c !== id);
setSelectedCards([...filteredIds]);
} else {
setSelectedCards([...selectedCards, id]);
}
return selectedCards;
};
const addUpCreditAvailable = (selectedCards) => {
const chosenCards = selectedCards.map((id) => {
const foundCard = allCards.find((card) => {
return card.id === id;
});
return foundCard;
});
const result = chosenCards.reduce((acc, card) => {
return acc + card.creditAvailable;
}, 0);
return result;
};
const totalCredit = addUpCreditAvailable(selectedCards);
render method:
render (
[...]
{selectedCards.length && (
<div className={bem(baseClass, "total-credit")}>
Total Credit available: £{totalCredit}
</div>
)}
[...]
)
I would like to remove an item from array onClick by using the prevState syntax.
Here is an example code that i use:
export default function App() {
const [selected, setSelected] = useState(false);
const [selectedItems, setSelectedItems] = useState([]);
const [cards, setCards] = useState(["card1", "card2", "card3", "card4"]);
function handleButtonClick(event) {
setSelected(true);
let key = event.target.id;
key = parseInt(key, 10);
if (selectedItems.indexOf(key) === -1) {
setSelectedItems([...selectedItems, key]);
} else {
// HOW do i remove the item from the array ????
let index = selectedItems.indexOf(key);
}
}
return (
<div className="App">
{cards.map((card, index) => (
<button
className={
selected && selectedItems.indexOf(index) !== -1
? "button1 active"
: "button1"
}
onClick={handleButtonClick}
id={index}
>
{card}
</button>
))}
</div>
);
}
link to sandbox as well : https://codesandbox.io/s/patient-thunder-mb1vx?file=/src/App.js
Once you've found the index of the key in selectedItems, set selectedItems's state by slicing the array before and after that index:
const index = selectedItems.indexOf(key);
setSelectedItems([
...selectedItems.slice(0, index),
...selectedItems.slice(index + 1)
]);
const { useState } = React;
const App = () => {
const [selected, setSelected] = useState(false);
const [selectedItems, setSelectedItems] = useState([]);
const [cards, setCards] = useState(["card1", "card2", "card3", "card4"]);
function handleButtonClick(event) {
setSelected(true);
let key = event.target.id;
key = parseInt(key, 10);
if (selectedItems.indexOf(key) === -1) {
let index = selectedItems.indexOf(key);
setSelectedItems([...selectedItems, key]);
} else {
const index = selectedItems.indexOf(key);
setSelectedItems([
...selectedItems.slice(0, index),
...selectedItems.slice(index + 1)
]);
}
}
return (
<div className="App">
{cards.map((card, index) => (
<button
className={
selected && selectedItems.indexOf(index) !== -1
? "button1 active"
: "button1"
}
onClick={handleButtonClick}
id={index}
key={index}
>
{card}
</button>
))}
</div>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
.App {
font-family: sans-serif;
text-align: center;
}
.active {
border: 2px solid green;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class="react"></div>
Also remember to use a unique key prop for lists, and to prefer const over let whenever possible.
Use this one. Hope it will work.
if (selectedItems.indexOf(key) === -1) {
setSelectedItems([...selectedItems, key]);
} else {
// HOW do i remove the item from the array ????
let index = selectedItems.indexOf(key);
const arr = selectedItems.filter((item, i) => i !== index);
setSelectedItems(arr);
}
Check this link https://codesandbox.io/s/stupefied-greider-m25le
You can make a copy of your array, splice the index from the copy then set the state with the new array.
const newArray = [...oldArray]
newArray.splice(index, 1)
setSelectedItems(newArray)
An alternative to #CertainPerformance using splice:
const copy = [...selectedItems];
const index = copy.indexOf(key);
copy.splice(index, 1);
setSelectedItems(copy);
An option using Array#splice that uses the function form of setState:
const [items, setItems] = React.useState([]);
const deleteItemAtIndex = (index) => setItems(items => {
const newItems = [...items];
newItems.splice(index, 1);
return newItems;
});
This has the advantage that it can be memoized (useCallback) without needing items as a dependency (since setItems is always stable and the function itself doesn't use items, only the callback does):
const deleteItemAtIndex = React.useCallback((index) => setItems(items => {
const newItems = [...items];
newItems.splice(index, 1);
return newItems;
}), []);
I find it odd that the simple, standard answer hasn't been posted:
function handleButtonClick(event) {
setSelected(true);
const key = parseInt(event.target.id, 10);
setSelectedItems(selectedItems => {
const itemIndex = selectedItems.indexOf(key);
if (itemIndex === -1) {
// New item, add it
selectedItems = [...selectedItems, key];
} else {
// New item, add it
selectedItems = selectedItems.filter((_, index) => index !== itemIndex); // ***
}
return selectedItems;
});
}
Notice that since this sets state based on existing state, it's best to use the callback form of the state setter.