use another useeffect result in another - javascript

New react developer here, here i have two useEffects, one of them(first one) is an object which contains a 'name' which i want to use in my second useEffect. Second useEffect contains an array of objects, these array of objects has 'billingName' and 'driverName', some of them has same value in them for example driverName: "james". What i want to do is in my second useEffect check if 'name' from first useEffect is same as 'driverName', only then do this 'setOrders(res);
setRenderedData(res);'
my error message: Property 'driverName' does not exist on type...
my object: {
id: "98s7faf",
isAdmin: true,
name: "james"}
my array:  [{billingName: "trump",driverName: "james"}, {billingName: "putin",driverName: "alex"}, {…}, {…}, {…}]
my code:
const [driver, setDriver] = useState<User>();
useEffect(() => {
api.userApi
.apiUserGet()
.then((res: React.SetStateAction<User | undefined>) => {
console.log(res);
setDriver(res);
});
}, [api.userApi]);
useEffect(() => {
api.caApi
.apiCaGet(request)
.then((res: React.SetStateAction<CaDto[] | undefined>) => {
if (driver?.name == res?.driverName) {
setOrders(res);
setRenderedData(res);
console.log(res);
}
});
}, [api.caApi]);

You can call api.caApi in then of api.userApi,
useEffect(() => {
api.userApi.apiUserGet().then((res1?: User ) => {
setDriver(res1);
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]);

Related

Assure all ids are unique with RxJS Observable pipe

I have an observable that I'd like to modify before it resolves, either using a map pipe or something similar to ensure that all ids within the groups array are unique. If cats is encountered twice, the second occurrence should become cats-1, cats-2 etc. These fields are being used to populate a HTML id attribute so I need to ensure they are always unique.
{
title: 'MyTitle',
description: 'MyDescription',
groups: [
{
id: 'cats',
title: 'SomeTitle'
},
{
id: 'dogs',
title: 'SomeTitle'
},
{
id: 'octupus',
title: 'SomeTitle'
},
{
id: 'cats',
title: 'SomeTitle'
},
]
}
Using an RxJs observable my code looks like the following:
getGroups() {
return this.http.get(ENDPOINT_URL)
}
I was able to achieve this using a map operator with a set but part of me feels like this isn't the correct pipe for this as the array is nested.
getGroups() {
return this.http.get(ENDPOINT_URL).pipe(
map(data => {
const groupIds = new Map();
data.groups.map(group => {
if (!groupIds.get(group.id)) {
groupIds.set(group.id, 1)
} else {
const updatedId = (groupIds.get(group.id) || 0) + 1;
groupIds.set(group.id, updatedId);
group.id = `${group.id}-${updatedId}`
}
return group
}
return data;
}
)
}
Is there a more efficient way to make this operation using a more appropriate pipe? I am worried this can become quite inefficient and significantly delay rendering of content while the observable resolves the conflicts. As of today I am unable to modify the actual content returned from the API so that is not an option unfortunately.
You could try something like this:
import { of, map } from 'rxjs';
import { findLastIndex } from 'lodash';
of({
title: 'MyTitle',
description: 'MyDescription',
groups: [
{
id: 'cats',
title: 'SomeTitle',
},
{
id: 'dogs',
title: 'SomeTitle',
},
{
id: 'cats',
title: 'SomeTitle',
},
{
id: 'octupus',
title: 'SomeTitle',
},
{
id: 'cats',
title: 'SomeTitle',
},
],
})
.pipe(
map((data) => ({
...data,
groups: data.groups.reduce((acc, group) => {
const lastElementIndex = findLastIndex(acc, (accGroup) => accGroup.id.startsWith(group.id));
if (lastElementIndex === -1) {
return [...acc, group];
}
const lastElement = acc[lastElementIndex];
const lastNameNumerator = lastElement.id.split('-')[1];
return [
...acc,
{
...group,
id: `${group.id}-${lastNameNumerator ? +lastNameNumerator + 1 : 1}`,
},
];
}, []),
}))
)
.subscribe(console.log);
Stackblitz: https://stackblitz.com/edit/rxjs-kcxdcw?file=index.ts
If the only requirement is to have the ids be unique, you could ensure uniqueness by appending the array index to each element's id.
getGroups() {
return this.http.get(ENDPOINT_URL).pipe(
map(data => {
const groups = data.groups.map(
(g, i) => ({...g, id: `${g.id}-${i}`})
);
return { ...data, groups };
})
);
}
Output of groups:
// groups: Array[5]
// 0: Object
// id : "cats-0"
// title : "SomeTitle"
//
// 1: Object
// id : "dogs-1"
// title : "SomeTitle"
//
// 2: Object
// id : "cats-2"
// title : "SomeTitle"
//
// 3: Object
// id : "octupus-3"
// title : "SomeTitle"
//
// 4: Object
// id : "cats-4"
// title : "SomeTitle"
Here's a little StackBlitz.
Honestly what you have is probably fine. Here's another method that's slightly simpler. It first uses reduce to create an object literal of groups. If you were open to external dependencies you could use Ramda's groupWith function to produce the same result. Then it uses flatMap to flatten the groups. If there is only one item in the array then it is returned as is, otherwise the elements are mutated with the new ids.
getGroups() {
return this.http.get(ENDPOINT_URL).pipe(
map(data => Object.values(
data.groups.reduce((acc, cur) => {
(acc[cur.id] || (acc[cur.id] = [])).push(cur);
return acc;
},
{} as Record<string | number, [] as GroupType[])
).flatMap(grp => (grp.length === 1)
? grp
: grp.map((x, i) => ({ ...x, id: `${x.id}-${i + 1}`)))
)
}
Another one
map((data:any) => {
//create an array in the way [{id:"cats",data:[0,3]}{id:"dogs",data:[1]..]
const keys=data.groups.reduce((a:any,b:any,i:number)=>{
const el=a.find(x=>x.id==b.id)
if (el)
el.data=[...el.data,i]
else
a=[...a,({id:b.id,data:[i]})]
return a
},[])
//loop over groups, if keys.data.length>1 ...
data.groups.forEach((x,i)=>{
const el=keys.find(key=>key.id==x.id)
if (el.data.length>1)
x.id=x.id+'-'+(el.data.findIndex(l=>l==i)+1)
})
return data;
})
Or
map((data:any) => {
//create an object keys {cats:[0,3],dogs:[1]....
const keys=data.groups.reduce((a:any,b:any,i:number)=>{
if (a[b.id])
a[b.id]=[...a[b.id],i]
else
a[b.id]=[i]
return a
},{})
//loop over groups, if keys[id].length>0 ...
data.groups.forEach((x,i)=>{
if (keys[x.id].length>1)
x.id=x.id+'-'+(keys[x.id].findIndex(l=>l==i)+1)
})
return data;
})

How to mutate complex object with nested array with SWR?

TL;DR:
How can I spread a new object inside a nested array e.g. data.posts.votes and also keep all the existing state from data
Full Info:
I have data I fetch that looks like that:
I want to have an upvote functionality that adds a new vote object into the votes array in the screenshot above.
​
I really have no plan right now how to do this in the mutate function from swr:
Right now I have it like this but that won't work:
mutate(
`/api/subreddit/findSubreddit?name=${fullSub.name}`,
(data) => {
console.log(data);
return {
...data,
posts: (data.posts[postid].votes = [
...data.posts[postid].votes,
{ voteType: "UPVOTE" },
]),
};
},
false
);
How can I optimistically update this?
I think your approach should be like this:
mutate(
`/api/subreddit/findSubreddit?name=${fullSub.name}`,
async (data) => {
console.log(data);
return {
...data,
posts: data.posts.map((post) => {
if (post.id === postid) {
return {
...post,
votes: [...post.votes, { voteType: "UPVOTE" }],
};
} else {
return post;
}
}),
};
},
false
);
Try using immer
produce(data, draft => {
const post = draft.posts.find( p => p.id === postId);
post.votes.push(newVote);
})
All immutable without the clunky spreads.

How to get JSON object to React component?

I created a React App with AXIOS. I need to get some JSON data from back end and change the State with that data. When I get the object and mapping to my state, the state is only setting for the last element of the object. So I can only see the last element in the state. How I can get all the elements to the state?
My API call is as follows
API.post('viewallusers', [], config)
.then(({ data }) => {
const allUsers = data.response.AllUsers;
allUsers
.map(user => {
return (
this.setState({
data: [
createData(
user.name,
user.id,
user.email
)
]
})
)
})
})
.catch((err) => {
console.log("AXIOS ERROR: ", err);
})
JSON data:
{response :
{AllUsers :
0 : {name: "Amy", id: 1, email: "myEmail1"},
1 : {name: "Doris", id: 2, email: "myEmail2"},
2 : {name: "Jase", id: 3, email: "myEmail3"}
}
}
I expect the the state "data" is to be set as follows:
data : [
createData("Amy",1,"myEmail1"),
createData("Doris",2,"myEmail2"),
createData("Jase",3,"myEmail3")
]
But the actual state after getting the JSON data is
data : [
createData("Jase",3,"myEmail3")
]
How can I solve this?
You need to first map the data then set entire state.
API.post('viewallusers', [], config)
.then(({ data }) => {
this.setState({
data: data.response.AllUsers.map(user => (createData(user.name, user.id, user.email)))
})
})
Or use callback version of setState and manually merge state.data (NOT recommended in this particular case)
API.post('viewallusers', [], config)
.then(({ data }) => {
data.response.AllUsers.forEach(user => {
this.setState(prev =>
({...prev, data: [prev.data, createData(user.name, user.id, user.email)]})
)
})
})
It probably happens because setState doesn't do a deep merge. So if you have in state
state = {
key1: 123,
key2: {
test1: 1,
test2: 2
}
}
And you do
this.setState({
key2: {
test1: 4
}
})
You will end up with
state = {
key1: 123,
key2: {
test1: 4
}
}
You have to do instead:
this.setState((ps) => ({
key2: {
...ps.key2,
test1: 4
}
}));
Similar approach works if value for key2 is array. Or alternatively you can first map all the data and then do a setState as suggested in other answer.

Array prop returns Observer so can't access at [0]

I passed Array but got Observer here's my code:
In Component1
data() {
return {
myWords: [],
}
}
//...
await axios.post(this.serverUrl + router, {
voca: text,
category: this.buttonGroup.category.text
})
.then(res => {
this.myWords.push({
voca: this.voca,
vocaHeader: this.vocaHeader,
category: res.data.savedVoca.category,
date: res.data.savedVoca.date,
id: res.data.savedVoca._id
})
this.myWords.push({voca:"test"})
})
.catch(err => {
console.log(err)
})
In Component2
props: {
myWordsProp: {
type: Array,
default: () => ([])
},
},
mounted() {
console.log(this.myWordsProp)
console.log(this.myWordsProp[0]) //returns undefined
},
And I expected an Array but I get Observer so I can't get values from this.myWordsProp[0] why?
//this.myWordsProp
[__ob__: Observer]
0: {
category: "ETC"
date: "2018-11-21T15:31:28.648Z"
id: "5bf57a503edf4e0016800cde"
voca: Array(1)
vocaHeader: Array(1)
...
}
1: {__ob__: Observer}
length: 2
__ob__: Observer {value: Array(2), dep: Dep, vmCount: 0}
__proto__: Array
//this.myWordsProp[0]
undefined
I found a clue that when I test it outside of axios it worked as I expected.
Vue wraps data and props into reactive objects. Use vue-devtools plugin in your browser as an alternative to viewing the ugly observer in the console.
In your code, the object behaves correctly. It’s only in the console that it ‘looks’ different.
Anyway, you can also click on the ... to expand the node and get the value from the console.
https://github.com/vuejs/vue-devtools
I found a solution It's because of sending props before get data from server.
This is my whole of postVocas function It returns promise
postVocas: function (voca) {
if (!voca || voca.length < 1) return
let router = "/api/voca"
let text = ""
text += `${this.vocaHeader[0].english}, ${this.vocaHeader[0].korean}\n`
voca.forEach((x, index) => {
text += `${voca[index].english}, ${voca[index].korean}\n`
})
return axios.post(this.serverUrl + router, {
voca: text,
category: this.buttonGroup.category.text
}).then(res => {
this.myWords.push({
voca: this.voca,
vocaHeader: this.vocaHeader,
category: res.data.savedVoca.category,
date: res.data.savedVoca.date,
id: res.data.savedVoca._id
})
}).catch(err => {
console.log(err)
})
},
And await till get data from server.
This one is function where execute My postVocas function.
sendVocaToTable: async function () {
let reformedText = this.reformText(this.text)
this.voca = this.formatTextToVoca(reformedText)
await this.postVocas(this.voca)
this.$router.push({
name: 'Table',
params: {
vocaProp: this.voca,
tableHeaderProp: this.vocaHeader
}
})
},

Reading Object returns undefined Angular 4

Arr1 [
{id: 300,
uploads: [
{idOne: value},
{idTwo: value}
]
},
{blah: value,
uploads: [
{idOne: value},
{idTwo: value}
]
}
]
This Object is being read like this
this.activatedRoute.parent.params
.switchMap((params: Params) => this.someService.getSubmissions(+params['id']))
.subscribe(submissions => console.log('subs', submissions.map(x=>x)))
results in
The Object as shown above.
And
this.activatedRoute.parent.params
.switchMap((params: Params) => this.someService.getSubmissions(+params['id']))
.subscribe(submissions => console.log('subs', submissions.map(x=>x.id)))
Displays id:300
But when I go
this.activatedRoute.parent.params
.switchMap((params: Params) => this.someService.getSubmissions(+params['id']))
.subscribe(submissions => console.log('subs', submissions.map(x=>x.uploads)))
It logs undefined. And I can not reasonably detect why.
This is within the onInit() of an Angular component.
Try this, updated the above with forEach:-
this.activatedRoute.parent.params.switchMap(params => {
this.someService.getSubmissions(+params['id']))
.subscribe(submissions => {
submissions.forEach(x=>{
if(x.id){
console.log("id",x.id);
}
if(x.uploads){
console.log("uploads",x.uploads);
}
})
});
});

Categories