How can I update nested array? - javascript

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} />

Related

React Native infinite loop with object array in useEffect

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.

Using state hooks to conditionally update an array

I want to add items to an array with the useState hook instead of doing array.push. This is the original code:
let tags = []
data.blog.posts.map(post => {
post.frontmatter.tags.forEach(tag => {
if (!tags.includes(tag)){
tags.push(tag)
}
})
})
This is one of several things I've tried with React:
const [tags, setTags] = useState([])
data.blog.posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!tags.includes(tag)){
setTags(tags => [...tags, tag])
}
})
})
The "tags" state variable does not receive anything in the above example.
I have looked at a variety of similar threads but the problems and solutions there are difficult to translate to this situation.
You can try setting the tags state in initial render or on any event as per your requirement .
const [tags, setTags] = useState([]);
useEffect(()=>{
const arr=[];
data.blog.posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!arr.includes(tag)){
arr.push(tag)
}
})
});
setTags([...arr]);
},[]);
Ok, I did understand what you wanted to do.
Here is the code and I did add some commest and there is also a working code sandbox
so it will show the "tags" you have on your state and when you click on the button it will filter and add those tags that are missing
import React, { useState } from "react";
//mock data.blog.posts
const data = {
blog: {
posts: [
{
frontmatter: {
tags: ["tag1", "tag2", "tag3"]
}
}
]
}
};
const App = () => {
const [tags, setTags] = useState(["tag1"]);
const filterTags = () => {
const myTags = ["tag1"];
let result;
data.blog.posts.map((post) => {
// check what tags are not included in tag stateon line 18
result = post.frontmatter.tags.filter((item) => !tags.includes(item));
});
// here it will show that 'tag2' and 'tag3' does not exist
console.log("result", result);
// here we are setting the state
setTags((oldState) => [...oldState, ...result]);
};
return (
<div className="App">
<h1>My tags</h1>
{tags.map((tag) => (
<h4>{tag}</h4>
))}
<button onClick={() => filterTags()}>add tags</button>
<hr />
<h1>My tags from posts</h1>
{data.blog.posts.map((posts) => {
return posts.frontmatter.tags.map((tag) => <div>{tag}</div>);
})}
</div>
);
};
export default App;
and here is the codeSandBox

Using .filter() for React's useState setter within a .forEach() loop [duplicate]

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>
</>
);
}

Remove a single item from the list with onClick

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);
}

useState variable not latest version in delete function, React

I have a list of div's that contain question fields. For every button click i add a new line of question fields and memorize in state the whole list and how many lines there are. I tried adding a delete button but when my delete functions it seems that the value from the state variable is remembered from when the line was made. How do i solve this so i can acces the full list in the HandleDelete function?
const OefeningAanvragenDagboek = () => {
const { t, i18n } = useTranslation()
const [questionCount, setQuestionCount] = useState(1)
const [questionList, setQuestionList] = useState([])
const createNewLine = () =>{
var newLine=
<div>
<Field type="text" name={"vraag" + questionCount}/>
<Field component="select" name={"antwoordMogelijkheid"+questionCount}>
<option value="">...</option>
<option value="open">{t('oefeningAanvragenDagboek.open')}</option>
<option value="schaal">{t('oefeningAanvragenDagboek.scale')}</option>
</Field>
<Field type="text" name={"type"+questionCount}/>
<button onClick={() => HandleDelete(questionCount-1)}>{t('assignmentCard.delete')}</button>
</div>
setQuestionList(questionList => [...questionList, newLine])
setQuestionCount(questionCount+1)
}
const HandleDelete = (index)=> {
console.log(questionList)
// setQuestionList(questionList.splice(index, 1))
}
return (
<div>
<button onClick={createNewLine}>{t('oefeningAanvragenDagboek.addQuestion')}</button>
{questionList}
</div>
)
}
Use functional setState as HandleDelete has closure on questionList
setQuestionList(questionList => questionList.splice(index, 1))
Both state and props received by the updater function are guaranteed to be up-to-date.
setState() in classes
You can pass event handler from container to child and then invoke event handler from client.
For example, let's say I have an app displaying list of items and each item have a delete button to remove them from the list. In this case, parent component will supply list of items and event handler to child component and then, child will be responsible for rendering and calling event handler.
Take a look at this codesandbox from which I am pasting following code:
import React, { useState } from "react";
import "./styles.css";
export function List(props) {
return (
<div>
{props.items.map((i, idx) => (
<div class="item" key={idx}>
{i} <span onClick={() => props.onDelete(idx)}>X</span>
</div>
))}
</div>
);
}
export default function App() {
const [items, setItems] = useState([
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6"
]);
const deleteItem = (index) => {
if (index >= 0 && index < items.length) {
const newItems = items.slice();
newItems.splice(index, 1);
setItems(newItems);
}
};
return (
<div className="App">
<List items={items} onDelete={deleteItem}></List>
</div>
);
}
Primarily addressing the issue on the OP's comments section, at the time of this writing which was given a bounty in addition to the question.
The SandBox with the issue: https://codesandbox.io/s/charming-architecture-9kp71?file=/src/App.js
Basically, the solution to this issue is to perform all operations on the parameter of the callback function. In the case of the sandbox issue I linked above, if you look at removeToast on the code below, the operations are being done on the list array.
Code with the issue:
export default function App() {
const [list, setList] = useState([]);
const removeToast = (id) => {
const newList = list.filter(({ toastId }) => toastId !== id);
setList([...newList]);
};
const addToast = () => {
const toastId = Math.random().toString(36).substr(2, 9);
const newList = [
...list,
{
toastId,
content: (
<>
<button onClick={() => removeToast(toastId)}>Hi there</button>
Hello {toastId}
</>
)
}
];
setList([...newList]);
};
return (
<>
<button onClick={addToast}>Show Toast</button>
<Toaster removeToast={removeToast} toastList={list} />
</>
);
}
However since removeToast has a closure on list, we need to do the filtering on the previous state which is, again, accessible via the first parameter of the callback of setState
The fix:
const removeToast = (id) => {
setList((prev) => {
return prev.filter(({ toastId }) => toastId !== id);
});
};
The Solution: https://codesandbox.io/s/suspicious-silence-r1n41?file=/src/App.js

Categories