vue js 2 - for loop in multiple rest calls fetchData - rest

I am trying to get wp-rest and Vuejs 2 to work together, so far things are coming along nicely apart from this one rest call that requires another request for the design to be complete. Essentially I want to be able to iterate / loop through the first request and dynamically change update the second request.
And my second question is performance, overall the rest calls are taking a bit longer to load - is there something I can do to optimize?
Context:
The first result data gives me an id, slug and title to all the posts I want to display only on the homepage as featured - through that id or slug I want to pass it to the second request - so I can pull in more information about those posts - like featured image and other meta field data.
<pre>export default {
name: 'work',
data () {
return {
loading: false,
page: null,
pagesingle: null,
error: null
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.$http.get('/cms/wp-json/wp/v2/pages/?slug=work&_embed')
.then(result => {
this.page = result.data
this.$http.get('/cms/wp-json/wp/v2/cases-studes/?slug=case-study-name').then(
result => this.pagesingle = result.data
);
})
}
}
}</pre>

I think you want to look at Promise.all. It will take an array of promises, wait for them all to complete, and then resolve with an array of results.
You would build your array of promises based on the array of slugs and ids in your first request. Maybe something like
const promises = result.data.articles.map((article) =>
this.$http.get(`/cms/wp-json/wp/v2/cases-studies/?slug=${encodeURIComponent(article.slug)}`)
);
Getting the results is as easy as
Promise.all(promises).then((results) => {
this.arrayOfSinglePages = results.map((result) => result.data);
});
Now your this.page has the array of id (and stuff) and this.arrayOfSinglePages has the page details for each of them in the same order.

Related

Best practices for re-fech an item in a list react-query

How do you guys handle the case:
Query return list of post
Then edit the post, so I just need to fetch detail of that post again
It might somehow be related to normalization but I think we can have some best-practice to handle this.
Maybe we can have a keys
List: ['post']
Post item: ['post', {id: number}]
Then when we fetch the list, we actually set data for the post items, and the ['list'] only save the id.
Does that make sense? or anyone have a better solution for this
You can either prefetch your posts using QueryClient.prefetchQuery and then display them by retrieving individual posts from cache by id or you can perform optimistic update using single query key like this:
const queryClient = useQueryClient();
const { mutate } = useMutation(someUpdateFn, {
onMutate: async post => {
await queryClient.cancelQueries(['posts']);
queryClient.setQueryData(['posts'], posts => {
const previousPosts = posts.filter(({ id }) => post.id !== id);
return [...previousPosts, post];
});
},
});
You can read more about optimistic updates here
At a certain point, you should just manage your own state, global or local, via your own array and such. Do fetches with axios and add, delete, modify your own cache.
I have tried some complicated stuff and sooner or later, doing react query and making it work isn't worth it in my opinion. However, for most cases, react query is just brilliant.

Update other slice after request

using RTK Query, how can one use the fetching functionality to update another the state of another slice?
Essentially I'm trying to keep all related-state next to each other, and thus after querying data with the useLazyGetQuery, I'd like to use the result and store it in an existing slices state.
Whilst something like the below works from an component, it makes the whole code messy
const [trigger, result, lastArg] = useLazyGetFiltersQuery();
React.useEffect(() => {
if (result.status === 'fulfilled' && result.isSuccess === true && result.isLoading === false) {
dispatch(updateData(result.data.map((pg) => ({ label: pg, value: pg }))));
}
}, [dispatch, result, result.isLoading, result.isSuccess, result.status]);
I'd rather want to setup some logic directly in the createApi method so that the resulting data is stored from the beginning in the slice I want.
Thanks
Generally, I would advise against copying data out of RTK-Query unless you have a very good reason: you will have to hand-write a lot of stuff, pretty much defeating the purpose of RTK-Query and you lose stuff like automatic cache collection of unused cache entries - from that point on you have to do that yourself.
Also, that data is already in Redux. Usually, you should not duplicate data in the store when you can avoid it.
That said, of course you can do it. For example, it is shown in the "Using extraReducers" authentication example:
const slice = createSlice({
name: 'auth',
initialState: { user: null, token: null } as AuthState,
reducers: {},
extraReducers: (builder) => {
builder.addMatcher(
api.endpoints.login.matchFulfilled,
(state, { payload }) => {
state.token = payload.token
state.user = payload.user
}
)
},
})
Keep in mind: this is Redux and everything happens via actions. You will never have to useEffect something to write it back into state. Just listen for the right actions in your reducers.

Dataloader did not return an array of the same length?

I Am building an express JS application with graphql, and mongodb (mongoose). I am using facebooks Dataloader to batch and cache requests.
Its working perfectly fine except for this use case.
I have a database filled with users posts. Each post contains the users ID for reference. When i make a call to return all the posts in the database. The posts are returned fine but if i try to get the user in each post. Users with multiple posts will only return a single user because the key for the second user is cached. So 2 posts(keys) from user "x" will only return 1 user object "x".
However Dataloader has to return the same amount of promises as keys that it recieves.
It has a option to specify cache as false so each key will make a request. But this doesnt seem to work for my use case.
Sorry if i havn't explained this very well.
this is my graphql request
query {
getAllPosts {
_id // This is returned fine
user {
_id
}
}
}
Returned error:
DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function did not return a Promise of an Array of the same length as the Array of keys.
are you trying to batch post keys [1, 2, 3] and expecting to get user results [{ user1 }, {user2}, { user1 }]?
or are you trying to batch user keys [1, 2] and expecting to get post results [{ post1}, {post3}] and [{ post2 }]?
seems like only in the second case will you run into a situation where you have length of keys differing from length of results array.
to solve the second, you could do something like this in sql:
const loader = new Dataloader(userIDs => {
const promises = userIDs.map(id => {
return db('user_posts')
.where('user_id', id);
});
return Promise.all(promises);
})
loader.load(1)
loader.load(2)
so you return [[{ post1}, {post3}], [{ post2 }]] which dataloader can unwrap.
if you had done this instead:
const loader = new Dataloader(userIDs => {
return db('user_posts')
.where('user_id', [userIDs]);
})
loader.load(1)
loader.load(2)
you will instead get [{ post1}, {post3}, { post2 }] and hence the error: the function did not return a Promise of an Array of the same length as the Array of keys
not sure if the above is relevant / helpful. i can revise if you can provide a snippet of your batch load function
You need to map the data returned from the database to the Array of keys.
Dataloader: The Array of values must be the same length as the Array of keys
This issue is well explained in this YouTube Video - Dataloader - Highly recommended

Meteor: Increment DB value server side when client views page

I'm trying to do something seemingly simple, update a views counter in MongoDB every time the value is fetched.
For example I've tried it with this method.
Meteor.methods({
'messages.get'(messageId) {
check(messageId, String);
if (Meteor.isServer) {
var message = Messages.findOne(
{_id: messageId}
);
var views = message.views;
// Increment views value
Messages.update(
messageId,
{ $set: { views: views++ }}
);
}
return Messages.findOne(
{_id: messageId}
);
},
});
But I can't get it to work the way I intend. For example the if(Meteor.isServer) code is useless because it's not actually executed on the server.
Also the value doesn't seem to be available after findOne is called, so it's likely async but findOne has no callback feature.
I don't want clients to control this part, which is why I'm trying to do it server side, but it needs to execute everytime the client fetches the value. Which sounds hard since the client has subscribed to the data already.
Edit: This is the updated method after reading the answers here.
'messages.get'(messageId) {
check(messageId, String);
Messages.update(
messageId,
{ $inc: { views: 1 }}
);
return Messages.findOne(
{_id: messageId}
);
},
For example the if(Meteor.isServer) code is useless because it's not
actually executed on the server.
Meteor methods are always executed on the server. You can call them from the client (with callback) but the execution happens server side.
Also the value doesn't seem to be available after findOne is called,
so it's likely async but findOne has no callback feature.
You don't need to call it twice. See the code below:
Meteor.methods({
'messages.get'(messageId) {
check(messageId, String);
var message = Messages.findOne({_id:messageId});
if (message) {
// Increment views value on current doc
message.views++;
// Update by current doc
Messages.update(messageId,{ $set: { views: message.views }});
}
// return current doc or null if not found
return message;
},
});
You can call that by your client like:
Meteor.call('messages.get', 'myMessageId01234', function(err, res) {
if (err || !res) {
// handle err, if res is empty, there is no message found
}
console.log(res); // your message
});
Two additions here:
You may split messages and views into separate collections for sake of scalability and encapsulation of data. If your publication method does not restrict to public fields, then the client, who asks for messages also receives the view count. This may work for now but may violate on a larger scale some (future upcoming) access rules.
views++ means:
Use the current value of views, i.e. build the modifier with the current (unmodified) value.
Increment the value of views, which is no longer useful in your case because you do not use that variable for anything else.
Avoid these increment operator if you are not clear how they exactly work.
Why not just using a mongo $inc operator that could avoid having to retrieve the previous value?

Query sailsjs blueprint endpoints by id array using request

I'm using the request library to make calls from one sails app to another one which exposes the default blueprint endpoints. It works fine when I query by non-id fields, but I need to run some queries by passing id arrays. The problem is that the moment you provide an id, only the first id is considered, effectively not allowing this kind of query.
Is there a way to get around this? I could switch over to another attribute if all else fails but I need to know if there is a proper way around this.
Here's how I'm querying:
var idArr = [];//array of ids
var queryParams = { id: idArr };
var options: {
//headers, method and url here
json: queryParams
};
request(options, function(err, response, body){
if (err) return next(err);
return next(null, body);
});
Thanks in advance.
Sails blueprint APIs allow you to use the same waterline query langauge that you would otherwise use in code.
You can directly pass the array of id's in the get call to receive the objects as follows
GET /city?where={"id":[1, 2]}
Refer here for more.
Have fun!
Alright, I switched to a hacky solution to get moving.
For all models that needed querying by id arrays, I added a secondary attribute to the model. Let's call it code. Then, in afterCreate(), I updated code and set it equal to the id. This incurs an additional database call, but it's fine since it's called just once - when the object is created.
Here's the code.
module.exports = {
attributes: {
code: {
type: 'string'//the secondary attribute
},
// other attributes
},
afterCreate: function (newObj, next) {
Model.update({ id: newObj.id }, { code: newObj.id }, next);
}
}
Note that newObj isn't a Model object as even I was led to believe. So we cannot simply update its code and call newObj.save().
After this, in the queries having id arrays, substituting id with code makes them work as expected!