Apollo/GraphQL/React, How to query for data in a loop? - javascript

In one of my React components, I have an array that includes a number of IDs as Strings. I want to query each ID in the array and return an array of its associated objects within my MongoDB database. However, this only happens conditionally, so I wanted to use useLazyQuery instead of useQuery.
I tried to accomplish it via mapping, but it seems I had an issue with using useLazyQuery. Here's the issue:
const [queryId, {loading, data}] = useLazyQuery[QUERY_ID];
if (queryForIds) {
// Using just the first ID
(queryId({variables: {id: idList[0]}}));
console.log(data);
}
This results in an infinite loop of nothing being printed (maybe my resolver returns nothing but regardless I get an infinite loop that crashes my site). I am most likely misusing useLazyQuery but I'm unsure of how.
Originally my idea was to do this:
const [queryId, {loading, data}] = useLazyQuery[QUERY_ID];
if (queryForIds) {
// Using just the first ID
idList.map((id) => (queryId({variables: {id: idList[0]}}))).data);
}
But I'm also unsure if that works either. How can I resolve either issue?

Related

RTKQ - Select data without hooks

I'm trying to select cached data from RTKQ without using the auto-generated query hook, but I'm having trouble understanding the docs
const result = api.endpoints.getPosts.select()(state)
const { data, status, error } = result
This is how the docs describe how to access the data, but I can't find any references on how to inject the state object "select()(state)".
I can't figure out how to access the data if I only call the select?
api.endpoints.getPosts.select()
Can someone explain me the difference between "select()" and "select()(state)"
Or what is the optimal solution to access the cached data from RTKQ?
The result of api.endpoints.getPosts.select() is a selector function for the result of using the "getPosts" endpoint without arguments.
Similarly, result of api.endpoints.getPosts.select({ page: 5 }) is a selector function for the result of using the "getPosts" endpoint the argument { page: 5 }.
A selector function is then called as selector(state) or passed into useSelector(selector).
If you write that altogether, you end up with api.endpoints.getPosts.select()(state).
#phry
Thank you for your answer! I'm not 100% sure I understood your answer. But it pointed me in a direction that enabled me to get the data.
I ended up creating a selector like the docs.
export const selectUser = (state) => userApi.endpoints.getUser.select()(state);
and in my function, I referenced it with getting the exported store from configureStore() method
const { data } = selectUser(store.getState());
But I'm not sure if this is the intended way to do it.

variable inside Observable subscription gets empty value

I know that Observables take some time to get data while javascript keeps running the others codes and that is troubling me a lot.
I have used ngrx in my angular project. Here, I am trying to fetch some data from the store which is working fine. Then, I convert this data stream into string[] which is also working fine.
To use this string[] me subscribeto this observable. And inside subscription I try to assign the value to other values named filterSizeValues.
Here, the problem comes. If I console.logthis filterSizeValuesinitially I got and empty array. When the observable finishes his job filterSizeValues variable is filled with data.
But I can not effort filterSizeValues variable to be empty array initially. What can I do?
I have already searched the solution in the internet but nothing is working out.
Help me out please. And Many Many Thanks in advance.
Here is my code;
this.sizeTargetingStore$.dispatch(SizeTargetingActions.getSizeTargeting({
campaignId: this.campaignId,
lineItemId: this.lineItemId
}));
Here I am accessing the store to get data.
this.sizeTargeting$
.pipe(switchMap(sizes=>{
let temporary:string[] = [];
sizes.forEach(eachSize=>{
temporary.push(eachSize.name);
})
this.filterSizeValues$ = of(temporary);
return this.filterSizeValues$;
}))
.subscribe(size_name=>{
this.filters.set('size_name', size_name);
})
Here, I am trying to set the filter values.
I also tried this way also.
this.sizeTargeting$
.pipe(switchMap(sizes=>{
let temporary:string[] = [];
sizes.forEach(eachSize=>{
temporary.push(eachSize.name);
})
this.filterSizeValues$ = of(temporary);
return this.filterSizeValues$;
}))
.subscribe(size_name=>{
this.filterSizeValues = size_name
})
this.filters.set('size_name', this.filterSizeValues);
But all ways filters set to an empty array.
Anyone can help me out please?
From my understanding, you have 2 possibilities, either filter out the empty values or skip the first value. You can do so with the filter and skip rxjs operator respectively.
Also I believe that you are misusing the switchMap operator, since you are not using asynchronous operations within your switchMap we can use the map operator instead, so below I have a simplified version of your code with your 2 options to fix your problem.
Option 1:
this.sizeTargeting$.pipe(
filter(sizes => sizes.length > 0), // filter out empty array values
map(sizes => sizes.map(size => size.name)) // perform your remap
).subscribe(sizes => {
this.filterSizeValues = size_name; // Only arrays with values will reach this step
});
Option 2:
this.sizeTargeting$.pipe(
skip(1), // skip the first value
map(sizes => sizes.map(size => size.name)) // perform your remap
).subscribe(sizes => {
this.filterSizeValues = size_name; // Only arrays with values will reach this step
});
Normally when I subscribe to something that I am waiting on to return what I do is I set up a Subject:
private componentDestroyed$ = new Subject<void>();
then in the Observable piping and subscription I do it as:
this.sizeTargeting$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((sizes: YourTypeHere[]) => {
if(sizes) {
//Do what I need to do with my sizes here, populate what I need,
//dispatch any other actions needed.
}
})

Using Merge with a single Create call in FaunaDB is creating two documents?

Got a weird bug using FaunaDB with a Node.js running on a Netlify Function.
I am building out a quick proof-of-concept and initially everything worked fine. I had a Create query that looked like this:
const faunadb = require('faunadb');
const q = faunadb.query;
const CreateFarm = (data) => (
q.Create(
q.Collection('farms'),
{ data },
)
);
As I said, everything here works as expected. The trouble began when I tried to start normalizing the data FaunaDB sends back. Specifically, I want to merge the Fauna-generated ID into the data object, and send just that back with none of the other metadata.
I am already doing that with other resources, so I wrote a helper query and incorporated it:
const faunadb = require('faunadb');
const q = faunadb.query;
const Normalize = (resource) => (
q.Merge(
q.Select(['data'], resource),
{ id: q.Select(['ref', 'id'], resource) },
)
);
const CreateFarm = (data) => (
Normalize(
q.Create(
q.Collection('farms'),
{ data },
),
)
);
This Normalize function works as expected everywhere else. It builds the correct merged object with an ID with no weird side effects. However, when used with CreateFarm as above, I end up with two identical farms in the DB!!
I've spent a long time looking at the rest of the app. There is definitely only one POST request coming in, and CreateFarm is definitely only being called once. My best theory was that since Merge copies the first resource passed to it, Create is somehow getting called twice on the DB. But reordering the Merge call does not change anything. I have even tried passing in an empty object first, but I always end up with two identical objects created in the end.
Your helper creates an FQL query with two separate Create expressions. Each is evaluated and creates a new Document. This is not related to the Merge function.
Merge(
Select(['data'], Create(
Collection('farms'),
{ data },
)),
{ id: Select(['ref', 'id'], Create(
Collection('farms'),
{ data },
)) },
)
Use Let to create the document, then Update it with the id. Note that this increases the number of Write Ops required for you application. It will basically double the cost of creating Documents. But for what you are trying to do, this is how to do it.
Let(
{
newDoc: Create(q.Collection("farms"), { data }),
id: Select(["ref", "id"], Var("newDoc")),
data: Select(["data"], Var("newDoc"))
},
Update(
Select(["ref"], Var("newDoc")),
{
data: Merge(
Var("data"),
{ id: Var("id") }
)
}
)
)
Aside: why store id in the document data?
It's not clear why you might need to do this. Indexes can be created on the ref value themselves. If your client receives a Ref, then that can be passed into subsequent queries directly. In my experience, if you need the plain id value directly in an application, transform the Document as close to that point in the application as possible (like using ids as keys for an array of web components).
There's even a slight Compute advantage for using Ref values rather than re-building Ref expressions from a Collection name and ID. The expression Ref(Collection("farms"), "1234") counts as 2 FQL functions toward Compute costs, but reusing the Ref value returned by queries is free.
Working with GraphQL, the _id field is abstracted out for you because working with Document types in GraphQL would be pretty awful. However, the best practice for FQL queries would be to use the Ref's directly as much as possible.
Don't let me talk in absolute terms, though! I believe generally that there's a reason for anything. If you believe you really need to duplicate the ID in the Documents data, then I would be interested in a comment why.

Query regarding looping a response data array using async await

I am working with an existing class components based React application and was dumbfounded when I spent almost 3 hours to figure out the root cause of the following code snippet.
In componentDidMount which is async btw, I am calling backend API designed with Node.js using axios like this
(Note state below is in Class Component which I skipped for obvious reasons)
state = {
grades = [];
}
try {
const res = await axios.get(
`/assignments/getStudentAssignment/${this.props.studentId}`
);
const assignments = res.data.assignment.assignments;
assignments.forEach((assignment) => {
this.setState({grades: [...this.state.grades, assignment.grade]})
}))
} catch (err) {
'some logic'
}
Where res.data.assignment.assignments is an array of each student assignments, and from each assignment I am pushing a new grade in grades array state. In console.log using Chrome browser I saw that the grades array had 8 elements but I couldn't view them in console.log, and the length of array was also set to 0.
I consoled assignment within forEach array and also set a counter in order to see that is forEach looping through the entire array or just first element. Console.log only printed first counter number and also first array element
I figured out that since the assignments is an array of promises that are yet to be resolved, I cannot loop through the entire array, and then I changed my code to this
await assignments.forEach((assignment) => {
this.setState({grades: [...this.state.grades, assignment.grade]})
}))
And viola, it worked like a charm but I am confused on a couple of things.
Isn't await supposed to resolve promise res.data.assignment.assignments,
meaning I can loop through resolved array data
Why did forEach work for only first element of
res.data.assignment.assignments if the array was yet to be resolved?
I tried on MDN, by creating a sample array in a promise and then looping through the contents of array in a then chain where each element was printed on console perfectly. Any help will be appreciated
EDIT
I have changed the original code, axios returns an assignment object which also contains an array of assignments
I worked with the following code once again without await, and its working fine. This is the second time, I had trouble with React setState, first time setState wasn't firing for some reason, and then after deleting node_modules and installing all required packages it worked, even though it wasted a day
state = {
grades = [];
}
try {
const res = await axios.get(
`/assignments/getStudentAssignment/${this.props.studentId}`
);
const assignments = res.data.assignment.assignments;
assignments.forEach((assignment) => {
this.setState({grades: [...this.state.grades, assignment.grade]})
}))
} catch (err) {
'some logic'
}

How to execute a sort after loading in data into an array in UseEffect - React Native

I'm trying to create a chat app and there is a small issue. Whenever I load in my messages from firebase, they appear in the chat app in unsorted order, so I'm attempting to sort the messages by timestamp so they appear in order. I can do this if I move the sort and setMessages within onReceive of useEffect, but I feel like this will be pretty inefficient because it sorts and setsMessages a separate time for each message that's retrieved from firebase. I want to just do it all at the end after all the messages are loaded into the array.
Right now with my logs, I get this:
[REDACTED TIME] LOG []
[REDACTED TIME] LOG pushing into loadedMessages
[REDACTED TIME] LOG pushing into loadedMessages
So it's printing the (empty) array first, then loading in messages. How can I make sure this is done in the correct order?
useEffect(() => {
// Gets User ID
fetchUserId(getUserId());
const messagesRef = firebase.database().ref(`${companySymbol}Messages`);
messagesRef.off();
messagesRef.off();
const onReceive = async (data) => {
const message = data.val();
const iMessage = {
_id: message._id,
text: message.text,
createdAt: new Date(message.createdAt),
user: {
_id: message.user._id,
name: message.user.name,
},
};
loadedMessages.push(iMessage);
console.log('pushing into loadedMessages');
};
messagesRef.on('child_added', onReceive);
loadedMessages.sort(
(message1, message2) => message2.createdAt - message1.createdAt,
);
console.log(loadedMessages);
return () => {
console.log('useEffect Return:');
messagesRef.off();
};
}, []);
I think that the perspective is a bit off.
The right way to do so will be to fetch the firebase data sorted.
Firebase has a built-in sort, although it does come with its limitations.
In my opinion, you sould try something like:
const messagesRef = firebase.database().ref(`${companySymbol}Messages`);
messagesRef.orderByChild("createdAt").on("child_added", function(snapshot) {
// the callback function once a new message has been created.
console.log(snapshot.val());
});
And if I may add one more thing, to bring every single message from the down of time can be a bit harry once you've got over a thousand or so, so I would recommend limiting it. that can be achieved using the built-in limit function limitToLast(1000) for example.
Good luck!
Well, the name of the database is "Realtime Database". You are using the "child_added" listener which is going to be triggered every time a new object gets added to the Messages collection. The onReceive callback should do the sorting - otherwise the messages won't be in the correct order. Yes, that is inefficient for the first load as your "child_added" will most probably be triggered for every item returned from the collection and you'll be repeating sorting.
What you could explore as alternative is to have a .once listener: https://firebase.google.com/docs/database/web/read-and-write#read_data_once the first time you populate the data in your app. This will return all the data you need. After that is complete you can create your "child_added" listener and only listen for new objects. This way onReceive shouldn't be called that often the first time and afterwards it already makes sense to sort on every new item that comes in.
Also have a look at sorting: https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data
You might be able to return the messages in the correct order.
And also - if you need queries - look at firestore...

Categories

Resources