New react developer here, here i have two API's, first one gives an object :
{ id: "98s7faf", isAdmin: true, name: "james"}
second one gives an array of objects :
[
{ billingName: "trump", driverName: "james" },
{ billingName: "putin", driverName: "alex" },
{ billingName: "kalle", driverName: "james" },
{ billingName: "sussu", driverName: "trump" },
{ billingName: "vladimir", driverName: "james" },
]
my question is, when user goes to the page, the page should automatically check both API'S, from first api name and from second api driverName, and if those two have same value then take that specific object from an array and pass it to these:
setOrders(res);
setRenderedData(res);
so at the moment there are three objects (those which have value of james) inside an array which matches name from first api, so it should pass those, and the rest which have different value it should not let them pass. here is my code, what have i done wrong ? instead of some(({ driverName }) i need some kind of filter ?
useEffect(() => {
api.userApi.apiUserGet().then((res1?: User ) => {
return api.caApi.apiCaGet(request).then((res?: CaDto[])
=> {
if (res.some(({ driverName }) => driverName === res1?.name)) {
setOrders(res);
setRenderedData(res);
console.log(res);
}
});
});
}, [api.caApi, api.userApi]);
you need to filter your response to extract the right objects given your condition.
useEffect(() => {
api.userApi.apiUserGet().then((res1?: User ) => {
return api.caApi.apiCaGet(request).then((res?: CaDto[])
=> {
const filteredData = res?.filter(({ driverName }) => driverName === res1?.name);
if(filteredData?.length) {
setOrders(filteredData);
setRenderedData(filteredData);
console.log(filteredData);
}
});
});
}, [api.caApi, api.userApi]);
note: having 2 states that holds the same values (orders, renderedData) is not a good practice, you might consider to refactor your logic.
Related
I have an a state object in React that looks something like this (book/chapter/section/item):
const book = {
id: "123",
name: "book1",
chapters: [
{
id: "123",
name: "chapter1",
sections: [
{
id: "4r4",
name: "section1",
items: [
{
id: "443",
name: "some item"
}
]
}
]
},
{
id: "222",
name: "chapter2",
sections: []
}
]
}
I have code that adds or inserts a new chapter object that is working. I am using:
// for creating a new chapter:
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters,
newChapter // insert new object
]
}
})
And for the chapter update, this is working:
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters.map(ch => {
return ch.id === selectedChapterId
? {...ch, name: selectedChapter.name}
: ch
})
]
}
})
But for my update/create for the sections, I'm having trouble using the same approach. I'm getting syntax errors trying to access the sections from book.chapters. For example, with the add I need:
// for creating a new section:
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters,
...old.chapters.sections?
newSection // how to copy chapters and the sections and insert a new one?
]
}
})
I know with React you're supposed to return all the previous state except for what you're changing. Would a reducer make a difference or not really?
I should note, I have 4 simple lists in my ui. A list of books/chapters/sections/items, and on any given operation I'm only adding/updating a particular level/object at a time and sending that object to the backend api on each save. So it's books for list 1 and selectedBook.chapters for list 2, and selectedChapter.sections for list 3 and selectedSection.items for list 4.
But I need to display the new state when done saving. I thought I could do that with one bookState object and a selectedThing state for whatever you're working on.
Hopefully that makes sense. I haven't had to do this before. Thanks for any guidance.
for adding new Section
setSelectedBook( book =>{
let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
selectedChapter.sections=[...selectedChapter.sections, newSection ]
return {...book}
})
For updating a section's name
setSelectedBook(book=>{
let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
let selectedSection = selectedChapter.sections.find(sec => sec.id === selectedSectionId )
selectedSection.name = newName
return {...book}
})
For updating item's name
setSelectedBook(book =>{
let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
let selectedSection = selectedChapter.sections.find(sec => sec.id === selectedSectionId )
let selectedItem = selectedSection.items.find(itm => itm.id === selectedItemId)
selectedItem.name = newItemName
return {...book}
})
I hope you can see the pattern.
I think the map should work for this use case, like in your example.
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters.map(ch => {
return { ...ch, sections: [...ch.sections, newSection] }
})
]
}
})
In your last code block you are trying to put chapters, sections and the new section into the same array at the same level, not inside each other.
Updating deep nested state objects in React is always difficult. Without knowing all the details of your implementation, it's hard to say how to optimize, but you should think hard about different ways you can store that state in a flatter way. Sometimes it is not possible, and in those cases, there are libraries like Immer that can help that you can look in to.
Using the state object you provided in the question, perhaps you can make all of those arrays into objects with id for keys:
const book = {
id: "123",
name: "book1",
chapters: {
"123": {
id: "123",
name: "chapter1",
sections: {
"4r4": {
id: "4r4",
name: "section1",
items: {
"443": {
id: "443",
name: "some item"
}
}
}
}
},
"222": {
id: "222",
name: "chapter2",
sections: {},
}
]
}
With this, you no longer need to use map or find when setting state.
// for creating a new chapter:
setSelectedBook(old => {
return {
...old,
chapters: {
...old.chapters,
[newChapter.id]: newChapter
}
}
})
// for updating a chapter:
setSelectedBook(old => {
return {
...old,
chapters: {
...old.chapters,
[selectedChapter.id]: selectedChapter,
}
}
})
// for updating a section:
setSelectedBook(old => {
return {
...old,
chapters: {
...old.chapters,
[selectedChapter.id]: {
...selectedChapter,
sections: {
[selectedSectionId]: selectedSection
}
},
}
}
})
Please let me know if I misunderstood your problem.
For the following code block:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
item = {
...item,
...changes
}
})
console.log(items) // items NOT reassigned with changes
items.forEach((item, i) => {
items[i] = {
...item,
...changes
}
});
console.log(items) // items reassigned with changes
Why does reassigning the values right on the element iteration not change the objects in the array?
item = {
...item,
...changes
}
but changing it by accessing it with the index does change the objects in the array?
items2[i] = {
...item,
...changes
}
And what is the best way to update objects in an array? Is items2[i] ideal?
Say no to param reassign!
This is a sort of a fundamental understanding of higher level languages like JavaScript.
Function parameters are temporary containers of a given value.
Hence any "reassigning" will not change the original value.
For example look at the example below.
let importantObject = {
hello: "world"
}
// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
parameter = {
...parameter,
updated: "object"
}
}
tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);
As you can see when you re-assign a parameter the original value will not be touched. There is even a nice Lint rule since this is a heavily bug prone area.
Mutation will work here, but ....
However if you "mutate" the variable this will work.
let importantObject = {
hello: "world"
}
// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
parameter["updated"] = "object"
}
tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);
So coming back to your code snippet. In a foreach loop what happens is a "function call" per each array item where the array item is passed in as a parameter. So similar to above what will work here is as mutation.
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
// Object assign just copies an object into another object
Object.assign(item, changes);
})
console.log(items)
But, it's better to avoid mutation!
It's better not mutate since this can lead to even more bugs. A better approach would be to use map and get a brand new collection of objects.
const items = [{
id: 1,
name: 'one'
},
{
id: 2,
name: 'two'
},
];
const changes = {
name: 'hello'
}
const updatedItems = items.map((item, i) => {
return {
...item,
...changes
}
})
console.log({
items
})
console.log({
updatedItems
})
As the MDN page for forEach says:
forEach() executes the callbackFn function once for each array
element; unlike map() or reduce() it always returns the value
undefined and is not chainable. The typical use case is to execute
side effects at the end of a chain.
Have a look here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
This means that although you did create new object for item, it was not returned as a value for that index of array. Unlike your second example, the first one is not changing original array, but just creates new objects and returns undefined. This is why your array is not modified.
I'd go with a classic Object.assign for this:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach( (item) => Object.assign(item,changes) )
console.log(items)
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
The other approach you can take is to use map and create a new array based on the original data and the changes:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
const newItems = items.map((item) => {
...item,
...changes
})
console.log(newItems);
But if you need to modify the original array, it's either accessing the elements by index, or Object.assign. Attempting to assign the value directly using the = operator doesn't work because the item argument is passed to the callback by value not by reference - you're not updating the object the array is pointing at.
One data set is an object of arrays of ids and another is an object of arrays of ids and names. What I'd like to do is check if the ids from the first data exist in the second data set and if they do then display the names.
This is what is being called by the component, which works correctly:
<td>Genre</td>
<td>{this.matchGenres(this.props.movie.genre_ids, this.props.genres)}</td>
And this is the function that I can't get to work:
matchGenres = (genres, genreList) => {
genres.forEach((genre) => {
genreList.filter((list) => {
return list.id === genre;
}).map((newList) => {
return newList.name;
});
});
}
It looks like the operation performs correctly and returns the right names when I console.log it! But! its not showing up in the component on render.
const genres = [{
id: 1,
name: "Jazz Music"
}, {
id: 2,
name: "Something"
}];
const genreList = [1, 10, 100];
matchGenres = (genres, genreList) => genres
.filter(genre => genreList.includes(genre.id))
.map(genre => genre.name);
const matchedGenres = matchGenres(genres, genreList);
console.log(matchedGenres);
But! its not showing up in the component on render.
Its because your function doesn't return anything. You return inside filter and map and your function does not return anything. Also note that forEach always return undefined
You just need a minor change. Try this
let genres = ["1", "2", "3"];
let genreList = [{
id: "2",
name: "Two"
}, {
id: "32",
name: "Three"
}]
matchGenres = (genres, genreList) => {
return genreList.filter((list) => {
// findIndex return array index if found else return -1 if not found
return genres.findIndex(genere => genere === list.id) > -1;
}).map(list => list.name);
}
console.log(matchGenres(genres, genreList));
This is the solution that ended up working:
if (genreList.length !== 0) {
return genres.map(genre => genreList.find(list => list.id === genre)).map((newList) => newList.name) + ',';
}
For some reason the value of GenreList, which is an array, was showing up as empty for the first couple times the function is call. Thats another problem I'll have to look at but the if statement solves for it for the time being.
Currently when I want to find single item in an array that is in store I use this:
this.matched = this.$store.state.itemlist.find(itemId=> {
return itemId.id == "someid";
});
Lets says I want to go over multiple arrays to find the matching item given provided ID? Like i have itemlist1 itemlist2 itemgetter()... Some of the arrays are getters ( but I think it doesnt change much). So basically I want to search over different state and getter items in this component instead of searching over one as in example above.
if you just want to find if its exist in one the arrays you can simply write function like this
function find(search,...arrs){
return arrs.flat(1).find(item => item == search)
}
this function merge all arrays to one long array and search in it
example of usage
let a=[1,2,3,4]
let b=[5,6,7,8]
let c=[9,10,11,12]
let i=find(6,a,b)
console.log(i)
Using one object to group all the arrays, so that will be possible to iterate over them. The idea is something like below:
const store = new Vuex.Store({
state: {
itemsGroupArrays: {
items1: [{ id: 1, text: "item1 - 1" }, { id: 2, text: "item1 - 2" }],
items2: [{ id: 3, text: "item2 - 1" }, { id: 4, text: "item2 - 2" }]
}
},
getters: {
getItemByIdFromStateGroupArrays: state => (id) => {
let returnedItem = null;
Object.values(state.itemsGroupArrays).forEach((itemStateArray) => {
if (itemStateArray.some(item => item.id === id)) {
returnedItem = itemStateArray.find(item => item.id === id);
}
})
return returnedItem;
}
}
});
I am confused about executing spread operator and using it to update state array like that
todos: [
{
id: "1",
description: "Run",
completed: "true"
},
{
id: "2",
description: "Pick John",
completed: "false"
}]
I have objects inside my array, the examples provided after searching are using spread operator to update arrays with single object, how can I update that object with "id" that equals "key" only. My wrong function is
markTaskCompleted(e) {
var key = e._targetInst.key;
this.setState(
{
todoList: // state.todoList is todos variable
[...this.state.todoList, this.state.todoList.map(task => {
if (task.id == key) task.completed = "true";
}) ]
},
this.getTodos
);
}
The result of this array is the same array of todos (spread operator) with array of undefined items.
I have been googling for some time but couldn't really get it.
Instead of destructuring the array and using map, I typically update a single item's value with a single map that replaces the item I am updating and returns the existing value for all other items. Something like this:
this.setState((prevState) => {
return {
todoList: prevState.todoList.map((task) => {
if (task.id === key) {
return { ...task, completed: true };
} else {
return task;
}
}),
};
});
Also, notice that this example passes a function to this.setState rather than an object. If you are updating the state based on the previous state (in this example using todoList from the previous state) you should use the function method. setState is asynchronous and you could get unexpected results from using this.state to compute the new state.