Why first element of array is empty? - javascript

I have a function which returns an Observable and once its fires I'm looping the subscribed array
this.productService.getProductBySeo(proudctName).pipe(
catchError(e => of(null)),
this.unsubsribeOnDestroy
).subscribe(res => {
if (res) {
this.categoriesService.currentCategory.subscribe(menus => {
const activeElem = res;
const allItems = menus;
allItems.map(item => {
console.log(item)
console.log(item.id) // can see id of each elem
console.log(item.children) // first element of item.children is empty
....
UPD 1
I have changed to switchMap as it was advised in comments , anyway the arrays is empty
this.productService.getProductBySeo(proudctName).pipe(
catchError(e => of(null)),
switchMap(res => {
this.categoriesService.currentCategory.subscribe(menus => {
if (menus) {
const activeElem = res;
const allItems = menus;
allItems.map(item => {
console.log(item)
// console.log(item.id) // can see id ,
console.log(item.children) // still empty :-(
item.children.map(item => {
// console.log(item);
return item
})
const fountObj = item.children.find(el => {
// console.log('element ', el)
return el === activeElem;
});
if (fountObj) {
this.totalStyle = item.seo_url; // to identify total style of the component
}
})
}
// console.log(menus)
})
return of(res)
}),
this.unsubsribeOnDestroy
).subscribe(res => {
if (res) {
this.product = res;
Regarding what do I expect to see is when subscription fires in this.categoriesService.currentCategory.subscribe, I want to see item.children in console. As I see only empty array when log item.children , but when I console just item, I can see that item.children has 2 items in own Array

Not a great solution, But I suspect that it might have been changed by reference on somewhere else. Try deep cloning and looping.
i.e.
const allItems = JSON.parse(JSON.stringify(menus));
allItems.forEach(item => {
console.log(item)
console.log(item.children)
Also, forEach looks much better than a map in such circumstances.

Related

Loop through an array list to make a post request for a rest API dynamic

I update an object. This object is an array which can have a length from one to five. This is my object ["max", "dog"].
Now a post method is to be called. It is so if the user has only 2 things filled in, only two things should be sent there. If 5 then 5 (see example for a better explanation.) Does anyone have an idea how best to set this up?
const tag = ["max", "dog"]
const updateInterest = () => {
axios
.post("...", {
first1: max,
first2: dog,
// first3: ...,
// first4: ...,
// first4: ...,
})
.then((res) => {
if (res.status === 200) {
// API update interest
}
})
.catch((error) => {
console.log(error);
});
};
What I try
const tag = ["max", "dog"]
const updateInterest = () => {
const object = "";
tags.map((tag, index) =>{
console.log(tag + " " + index)
object =
`first${index}`: `${tag}`,
})
axios
.post("...", {
object
})
.then((res) => {
if (res.status === 200) {
// API update interest
}
})
.catch((error) => {
console.log(error);
});
};
My loop doesn't really make much sense. How can I add this to an object so that it can be sent in later in an API?
You can use Object.fromEntries() to map an array of arrays to object, like in the following example:
const arr = ["max", "dog"];
const mapped = arr.map((el, i) => [`first${i+1}`, el]);
console.log(Object.fromEntries(mapped))
You can also use Array.prototype.reduce() to achieve the same thing:
const arr = ['cat', 'dog'];
const obj = arr.reduce((acc, cur, i) => ((acc[`first${i + 1}`] = cur), acc), {});
console.log(obj);
Reference: Convert Array to Object

Filtering array of objects by object property does not work in ReactJS

I want to delete an object from an array by a generated object id.
I've already tried multiple solutions on this and nothing is working.
Handler function:
handleDeleteTask = id => {
const task = this.state.tasks.find(i => {
return i.id === id;
});
const newTasks = this.state.tasks.splice(task, 1);
this.setState({ tasks: newTasks });
};
JSX:
{this.state.tasks.map(task => {
return (
<Task
key={task.id}
task={task.text}
title={task.title}
deleteTask={() => this.handleDeleteTask(task.id)}
/>
);
})}
Edited to reflect the suggestions from comments
splice returns the removed part. You could use filter instead:
handleDeleteTask = id => {
const newTasks = [...this.state.tasks].filter(x => x.id !== id);
this.setState({ tasks: newTasks });
};
Or spread the original state, splice the copy and set it as new state
handleDeleteTask = id => {
const task = this.state.tasks.findIndex(i => {
return i.id === id;
})
const newTasks = [...this.state.tasks]
newTasks.splice(task, 1)
this.setState({ tasks: newTasks });
};
Actually this could be written in one single line
const removeById = id => this.setState({tasks: [...this.state.tasks].filter(x=> x.id !== id)})
The first problem is, Array.prototype.splice needs the index of the item you want to delete as the first argument, not the item itself. To find the index instead, change this.state.tasks.find to this.state.tasks.findIndex.
The second problem is splice returns the removed portion of the array, not the new array, so you'd be setting your state to include just the removed item.
Perhaps a simpler approach would be to use Array.prototype.filter to remove the undesired item.
handleDeleteTask = id => {
this.setState({
tasks: this.state.tasks.filter(it => it.id !== id)
})
}

Is there a way to use a observable returning function for each element of another observable array?

I get an Observable<Group[]> from my Firebase collection.
In this Group class is an id which I wanna use to retrieve another dataset array from Firebase, which would be messages for each unique group Observable<Message[]>.(each group has its own chat: Message[])
And it want to return an observable which hold an array of a new Type:
return { ...group, messages: Message[] } as GroupWithMessages
the final goal should be Observable<GroupWithMessages[]>
getGroupWithChat(): Observable<GroupWithMessages[]> {
const groupColl = this.getGroups(); // Observable<Group[]>
const messages = groupColl.pipe(
map(groups => {
return groups.map(meet => {
const messages = this.getMessagesFor(group.uid);
return { messages:messages, ...group} as GroupWithMessages
});
})
);
return messages;
}
}
and here the Message function
getMessagesFor(id: string): Observable<Message[]> {
return this.afs.collection<Message>(`meets/${id} /messages`).valueChanges();
}
sadly that doesnt work because when i create the new Obj I cannot bind messages:messages because messages ist vom typ Observable<Message[]>
I hope that cleares things
UPDATE:
my main problem now comes down to this:
getGroupsWithMessages() {
this.getJoinedGroups()
.pipe(
mergeMap(groups =>
from(groups).pipe(
mergeMap(group => {
return this.getMessagesFor(group.uid).pipe(
map(messages => {
return { ...group, messages } as GroupIdMess;
})
);
}),
tap(x => console.log('reaching here: ', x)),
toArray(),
tap(x => console.log('not reaching here = completed: ', x))
)
),
tap(x => console.log('not reaching here: ', x))
)
.subscribe(x => console.log('not reaching here: ', x));
}
when i call that function my console.log is as follows:
Not sure if I follow what you're doing here but the logic look like you'd want:
getGroupWithChat() {
return this.getGroups.pipe(map(groups=> {
return groups.map(group => this.getMessagesFor(group.uid));
})).subscribe(); // trigger "hot" observable
}
Let me know if I can help further after you clarify.
UPDATE:
So it looks like you need to get the UID of the group before making the call to get the GroupMessages[]?
get Group: Observable
call getMessagesFor(Group.uid)
this example gets groups result$ then
concatMap uses groups result$ to make the messages query
this.getGroups().pipe(
concatMap((group: Group) => this.getMessagesFor(group.uid))
).subscribe((messages: GroupWithMessages[]) => {
console.log(messages);
});
You may still want to map them together but it seems like you know how to do that. concatMap waits for the first to finish, then makes the second call which you need.
Is this closer?
Use forkJoin to wait for messages to be received for all groups. Then map the result of forkJoin to an array of GroupWithMessages like this -
getGroupWithChat(): Observable<GroupWithMessages[]> {
return this.getGroups()
.pipe(
switchMap(groups => {
const messagesForAllGroups$ = groups.map(group => this.getMessagesFor(group.uid));
return forkJoin(messagesForAllGroups$)
.pipe(
map(joined => {
//joined has response like -
//[messagesArrayForGroup0, messagesArrayForGroup1, messagesArrayForGroup2....];
const messagesByGroup = Array<GroupWithMessages>();
groups.forEach((group, index) => {
//assuming that GroupWithMessages has group and messages properties.
const gm = new GroupWithMessages();
gm.group = group;
gm.messages = joined[index];
messagesByGroup.push(gm);
});
return messagesByGroup;
})
)
})
)
}
I usually do that by splitting Observable<any[]> to Observable<any> and then mergeMap the results to inner Observable.
Something like this should work:
getMessagesFor(id: string): Observable<number> {
return of(1);
}
getGroups(): Observable<string[]> {
return of(["1", "2"]);
}
getGroupWithChat() {
this.getGroups().pipe(
mergeMap(groups => from(groups)), // Split the stream into individual group elements instead of an array
mergeMap(group => {
return this.getMessagesFor(group).pipe(
map(messages => {
return Object.assign(group, messages);
})
);
})
);
}
Edit:
Consider BehaviorSubject. It doesn't complete at all:
const behSub: BehaviorSubject<number[]> = new BehaviorSubject([1, 2, 3]);
setTimeout(() => {
behSub.next([4, 5, 6]);
}, 5000);
behSub
.pipe(
mergeMap(arr =>
from(arr).pipe(
tap(), // Do something with individual items, like mergeMap to messages
toArray() // Go back to array
)
)
)
.subscribe(console.log, null, () => {
console.log('Complete');
});

REACT Todo-List : How to check if an item already exist in the array

I was trying a simple approach, But this approach doesn't seem to be working.
I want to check if the item exists when the button is clicked. using IF statement
//Adding Items on Click
addItem = () =>
{
let newValue = this.state.inputValue;
let newArray = this.state.inputArray;
if (newValue === newArray) {
console.log("Exist"); // this part doesnt work
} else {
newArray.push(newValue); //Pushing the typed value into an array
}
this.setState({
inputArray: newArray //Storing the new array into the real array
});
console.log(this.state.inputArray);
};
Change your function like below:
addItem = () =>
{
let newValue = this.state.inputValue;
let newArray = this.state.inputArray;
if (newArray.includes(newValue)) {
console.log("Exist");
return;
}
this.setState(previousState => ({
inputArray: [...previousState.inputArray, newValue]
}, () => console.log(this.state.inputArray)));
};
and don't push new value to state directly instead use it like below:
this.setState(previousState => ({
inputArray: [...previousState.inputArray, newValue]
}, () => console.log(this.state.inputArray)));
or
let inputArray= [...this.state.inputArray];
inputArray.push("new value");
this.setState({inputArray})

How to use array push with filter and map method in JavaScript

I have a filter method that filters an item from an array using an if condition. Using the filterArray I then use map method.
I would like to add a second condition and push a new array called OLD_ITEMS to the ITEMS array. How would I go about doing this?
import { OLD_ITEMS, ITEMS } from './constants';
let filterArray = ITEMS.filter(item => {
if (item.id === 'three') {
return false;
}
return true;
});
// TODO: add second condition here to push `OLD_TIMES` to `ITEMS`
const options = Object.assign(
...filterArray.map(({ id, name }) => ({ [id]: name }))
);
You need to be a little more clear about what you mean by "push" OLD_ITEMS to Items. Do you want to concatenate/push ALL of the OLD_ITEMS to the end of if a single condition is met, or do you want to push subset of OLD_ITEMS that meet a certain condition?
I believe that this is what you're looking for, but it's hard to know exactly:
import { OLD_ITEMS, ITEMS } from './constants';
let filterArray = ITEMS.filter(item => {
if (item.id === 'three') {
return false;
}
return true;
});
// TODO: add second condition here to push `OLD_TIMES` to `ITEMS`
const validOldItems = OLD_ITEMS.filter(item => {
if (item === 'some_condition') {
return false;
}
return true;
}
filterArray.push(validOldItems);
const options = Object.assign(
...filterArray.map(({ id, name }) => ({ [id]: name }))
);
Also, I highly recommend making your code more concise by returning the value of the conditional check, instead of an if/then
let filterArray = ITEMS.filter(item => {
return (item.id === 'three');
});
Or even more concise
let filterArray = ITEMS.filter(item => (item.id === 'three'));
Grand finale of conciseness:
const filterArray = ITEMS.filter(item => (item.id === 'three'))
.concat(OLD_ITEMS.filter(item => (item.id === 'some_condition'))
.map(({ id, name }) => ({ [id]: name }))
const options = Object.assign(...filterArray);

Categories