I have the following object with multiple object arrays. I would like to know how I can remove a single item from the list with onClick.
I know I can try it with the splice, but I have no idea how to do it within this object shape
State
const [filters, setFilters] = useState(initialData || [])
I tried like this
HandleClick
const handleClick = () => {
const newState = filters
const index = newState[key].findIndex((a) => a.id === id)
if (index === -1) return
newState[key].splice(index, 1)
setFilters(newState)
}
Data Object
{
"services": [
{
"id": "1b975589-7111-46a4-b433-d0e3c0d7c08c",
"name": "Account"
},
{
"id": "91d4637e-a17f-4b31-8675-c041fe06e2ad",
"name": "Income"
}
],
"accountTypes": [
{
"id": "1f34205b-2e5a-430e-982c-5673cbdb3a68",
"name": "Investment"
}
],
"channels": [
{
"id": "875f8350-073e-4a20-be20-38482a86892b",
"name": "Chat"
}
]
}
Render onClick
<div onClick={handleClick}>
<Icon fileName="smallClose" />
</div>
Maybe it is because you are not declaring the "id" variable anywhere? You can send the element id on the div event if it is on a map loop. I think that could fix your problem.
<div onClick={() => handleClick(id)}>
And then import it as a parameter on your function:
const handleClick = (id) => {
const newState = filters
const index = newState[key].findIndex((a) => a.id === id)
if (index === -1) return
newState[key].splice(index, 1)
setFilters(newState)
}
Also, when you declare the newState value, make sure to use spread operators on the declaration, in your current behavior you are mutating your state and that is not recommended in react:
const newState = [...filters];
when you print the compinent make sure to add them the id of your array
<div id={idOfItem} onClick={handleClick}>
<Icon fileName="smallClose" />
</div>
…….
function handleClick (e) {
e.preventDefault();
const {id} = e.target;
const list = yourItems.filter(item=> item.id!== id);
setItems(list);
}
Related
I have an array. And also I have contenteditable div. When I edit the div, I need update the array. arraywords is redux state
const arrayWords = [
[
["Hello",["Lello","Helli","Tello","Dello","Həll"]],
[" ",[]],
["guys", ["guya","rus","göy","kurs","suyu"]]]
]
My React component
const [content, setContent] = useState();
useEffect(() => {
setContent(element); // element is word
}, [element]);
const handleChange = (evt) => {
setContent(evt.target.value);
console.log(index);
};
return <ContentEditable html={content} className="editable-div" onChange={handleChange} />;
For example I need the array returns me hi, not hello or men instead of guys
const arrayWords = [
[
["Hi",["Lello","Helli","Tello","Dello","Həll"]],
[" ",[]],
["men", ["guya","rus","göy","kurs","suyu"]]]
]
This is child element. The parent element:
<Result element={i[0]} item={item} index={order} />
In my project, I need to get selected items from a Flatlist and pass them to my parent component.
I created a local state like this:
const [myState, setMyState] = useState<IStateType[] | []>([])
Each time an item is selected I try to add it to my useEffect:
useEffect(() => {
const result = myState.filter((el) => el.id !== item.id)
if (isSelected) {
setMyState([
...result,
{
propOne: 0,
propTwo: 1,
id: item.id,
...
},
])
} else {
setMyState(result)
}
}, [isSelected])
But I would need to put mySate in the dependency of my useEffect to add each time the new items selected. If I add it to the useEffect dependency it causes an infinite loop ^^
How to add each new item to my array while listening to all the changes and without causing an infinite loop?
I believe the issue you're having it's because you're not separating the concerns of each component correctly, once you have to relay on the previous data every time, the useEffect can be tricky. But there are two solutions to your issue:
Make use of useState callback function:
The useState function can be used with a callback rather than a value, as follows:
useEffect(() => {
if (isSelected) {
setMyState(prevState => [
...prevState,
{
propOne: 0,
propTwo: 1,
id: item.id,
...
},
])
} else {
setMyState(result)
}
}, [isSelected])
Best structure of your components + using useState callback function
What I could see about your approach is that you (as you showed) seems to be trying to handle the isSelected for each item and the myState in the same component, which could be done, but it's non-ideal. So I propose the creation of two components, let's say:
<List />: Should handle the callback for selecting an item and rendering them.
<List />:
function List() {
const [myState, setMyState] = useState([]);
const isItemSelected = useCallback(
(itemId) => myState.some((el) => el.id === itemId),
[myState]
);
const handleSelectItem = useCallback(
(itemId) => {
const isSelected = isItemSelected(itemId);
if (isSelected) {
setMyState((prevState) => prevState.filter((el) => el.id !== itemId));
} else {
setMyState((prevState) => prevState.concat({ id: itemId }));
}
},
[isItemSelected]
);
return (
<div>
<p>{renderTimes ?? 0}</p>
{items.map((item) => (
<Item
item={item}
onSelectItem={handleSelectItem}
selected={isItemSelected(item.id)}
/>
))}
</div>
);
}
<Item />: Should handle the isSelected field internally for each item.
<Item />:
const Item = ({ item, selected = false, onSelectItem }) => {
const [isSelected, setIsSelected] = useState(false);
useEffect(() => {
setIsSelected(selected);
}, [selected]);
return (
<div>
<p>
{item.name} is {isSelected ? "selected" : "not selected"}
</p>
<button onClick={() => onClick(item.id)}>
{isSelected ? "Remove" : "Select"} this item
</button>
</div>
);
};
Here's a codesnack where I added a function that counts the renders, so you can check the performance of your solution.
This question already has answers here:
Filter array of objects based on another array in javascript
(10 answers)
Closed last year.
I need to remove objects from an array stored within cart's state.
This should be done by iterating through the strings stored within selectedCart's array.
For each string for selectedCart that matches cart's string (within an object/key), it should remove that entire object.
The code below seems to only remove the object that matches selectedCart's string in last index ONLY.
So if it's
const [selectedCart, setSelectedCart] = ['test', 'this', 'data']
const [cart, setCart] = [{name: "this"}, {name: "data"}, {name: "test"}]
And each object's key (name) has the value of all these strings... only the string 'data' gets filtered.
If I switch the first and last positions (so 'test' is last), only the string 'test' gets filtered.
const [cart, setCart] = useState([])
const [selectedCart, setSelectedCart] = []
selectedCart.forEach(selected =>
setCart(cart.filter(entry => entry.name !== selected))
)
Edit: For the recommendation on another question regarding .filter(), it seems to only be applicable outside of useState().
When I take a similar approach with vanilla javascript, it seems to work fine.
You have to be careful with setting the state multiple times at once.
I think in this case you want to use a functional update like so
selectedCart.forEach(selected =>
setCart(cart => cart.filter(entry => entry.name !== selected))
)
Although speed-o-soundSonic's answer is probably ideal: setCart(cart.filter(entry => !selectedCart.includes(entry.name)))
here I am giving the full solution to your issue with UI.
Please check it out.
let cartArr = [...cart];
const selectedCartArr = [...selectedCart];
selectedCartArr.forEach((selected) => {
cartArr = cartArr.filter((el) => el.name !== selected);
});
setCart(cartArr);
setViewSelectedCart(selectedCartArr);
Kindly check my solution here:-https://codesandbox.io/s/react-filter-question1-y17dd?file=/src/App.js:851-961
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const dataArr = [
{ id: 1, name: "item a" },
{ id: 2, name: "item b" },
{ id: 3, name: "item c" },
{ id: 4, name: "item d" },
{ id: 5, name: "item e" }
];
const [cart, setCart] = useState([]);
const [selectedCart, setselectedCart] = useState([]);
const [viewSelectedCart, setViewSelectedCart] = useState([]);
useEffect(() => {
const data = [...dataArr];
setCart(data);
}, []);
const changeHandler = (event) => {
const _thisName = event.target.dataset.name;
const selectedCartArr = [...selectedCart];
selectedCartArr.push(_thisName);
setselectedCart(selectedCartArr);
};
const applyHandler = (event) => {
let cartArr = [...cart];
const selectedCartArr = [...selectedCart];
selectedCartArr.forEach((selected) => {
cartArr = cartArr.filter((el) => el.name !== selected);
});
setCart(cartArr);
setViewSelectedCart(selectedCartArr);
};
return (
<>
<div className="wrapper">
<div className="cart">
<h3>Cart</h3>
{cart.map((d) => {
return (
<div key={d.id}>
<input
type="checkbox"
id={d.id}
onChange={changeHandler}
data-name={d.name}
/>
<label htmlFor={d.id}>{d.name}</label>
</div>
);
})}
</div>
<div className="selectedCart">
<h3>Selected Cart View</h3>
<ol>
{viewSelectedCart.map((el, i) => {
return <li key={i}>{el}</li>;
})}
</ol>
</div>
</div>
<div className="button__wrapper">
<button className="btn btn-apply" onClick={applyHandler}>
Apply
</button>
</div>
</>
);
}
Below, i am rendering <App/> component with children as <Input/> component array. I added few inputs using "add new" button. I am able to add input text components. But, when i am typing value in text, it is not displaying. i am not able to modify object in state array since index is showing as "-1" in setData function. Due to this, value is not showing when we type in text box. Please let me know why state is [] when i am accessing in setData function.
function Input(props)
{
return (
<div>
<label htmlFor='variable'>Name</label>
<input id='variable'
type='text'
value={props.value}
onChange={(e) => props.setData(props.id, e.target.value)} />
</div>
)
}
function App()
{
let [state, setState] = React.useState([])
let [inputs, setInputs] = React.useState([])
let setData = ((id, value) =>
{
console.log(state); // prints []
let index = state.findIndex(ele => ele.key === id);
console.log(index); // prints -1
if (!(index === -1))
{
setState(state =>
{
state[idx]["value"] = value;
})
}
})
let handleAdd = () =>
{
let idx = `${new Date().getTime()}`
let tempState = {
"key": idx,
"value": "",
}
setState(state => [...state, tempState])
let input = <Input key={tempState.key}
value={tempState.value}
id={tempState.key}
setData={setData} />
setInputs(inputs => [...inputs, input])
}
return (
<div>
<button onClick={handleAdd}>add new</button>
<div>
{inputs}
</div>
</div>
)
}
When you create an Input component inside handleAdd, it creates a closure and as a result setData gets the state that existed when the component was created, missing the newly added state.
In general, creating components and saving them to state is not a good approach. Instead it's better to only save the data onto state and render the components based on it.
Here's one way to do this, note how much simpler the component and its logic are.
function App() {
let [state, setState] = React.useState([]);
let setData = (id, value) => {
const newState = state.map((st) => {
if (st.key === id) {
st.value = value;
}
return st;
});
setState(newState);
};
const addInput = () => {
const idx = `${new Date().getTime()}`;
setState([...state, { key: idx, value: '' }]);
};
return (
<div>
<button onClick={addInput}>add new</button>
<div>
{state.map((st) => (
<Input value={st.value} key={st.key} setData={setData} id={st.key} />
))}
</div>
</div>
);
}
So i want to build a voting app thing, and I need to set the number of votes into state so that it updates on click.But I don't exactly know how to set one property of an object without disrupting the other oroperty and the other object.
import './App.css';
import React, {useState} from 'react'
function App() {
const [votes, setVotes] = useState([
{
voteNum: 0,
name: "Item 1"
},
{
voteNum: 0,
name: "Item 2"
}
])
const addVote = (vote) => {
vote.voteNum++
setVotes( /* How do I set the votes.voteNum to be equal to vote.voteNum from the line above */)
}
return (
<div className="App">
<h1>Cast Your Votes!</h1>
{votes.map(vote => (
<form className="votee" onClick={() => addVote(vote)}>
<h1>{vote.voteNum}</h1>
<h2>{vote.name}</h2>
</form>
))}
</div>
);
}
export default App;
You should update react state in immutable way:
const addVote = (vote) => {
setVotes(votes.map(x => x === vote ? ({
...x,
voteNum: x.voteNum + 1
}) : x))
}
Or better use some unique Id in comparison, e.g. x.id === vote.id if you have Ids for your objects; or as mentioned in another answer, you can also use element array index in comparison (pass that to the function instead of vote).
const addVote = (vote) => {
const _votes = JSON.parse(JSON.stringify(votes))
const index = _votes.findIndex(v => v.name === vote.name)
if (index !== -1) {
_votes[index].voteNum ++
}
setVotes(_votes)
}
Pass index to function
{votes.map((vote, index) => (
<form className="votee" onClick={() => addVote(index)}>
<h1>{vote.voteNum}</h1>
<h2>{vote.name}</h2>
</form>
))}
addVote function would look like
const addVote = (index) => {
votes[index].voteNum ++;
setVotes([...votes]);
}
Note: Usestate is prefrered to use primitive data types i.e string, boolean and number
For array and object make use of useReducer