Apollo and AwsAppsync repeating mutation in callback - graphql

I have an awsAppSync client that is set up as follows:
that._getClient = function() {
return client = new AWSAppSyncClient({
url: appSyncUrl,
region: AWS.config.region,
auth: {
type: AUTH_TYPE.AWS_IAM,
credentials: AWS.config.credentials
}
});
}
And a mutate function that is called like this:
that.mutate = function(mutation, variables) {
let client = that._getClient();
return client.mutate({ mutation: mutation, fetchPolicy: 'no-cache', variables: variables })
.then(function (result){
return result;
});
}
I need to make subsequent queries to create different records that depend on one another, so I'm returning the Id of the newly created record to use in the callback of the previous query.
The problem is, that in every callback where the mutate function is called, the mutation that caused this callback gets executed again. For example, I do:
appSync.mutate(mutation, requestParams)
.then(function (response) {
let id = response.id
requestParams = {//new stuff}
return appSync.mutate(mutation, requestParams)
})
.then(//and so forth)
Now, I've seen some posts on here that say that it might be something to do with the optimistic response, and so on, but I actually get two new records in my database. I also considered the cache doing something trippy, but As you can see from the code, I (think) disabled it for the mutation.
Please help.

Related

How can I use Apollo's cacheRedirect with a nested query

I've got a query that looks like this:
export const GET_PROJECT = gql`
query GetProject($id: String!) {
homework {
getProject(id: $id) {
...ProjectFields
}
}
}
${ProjectFieldsFragment}
`;
My InMemoryCache looks like this:
const cache = new InMemoryCache({
dataIdFromObject: ({ id }) => id,
cacheRedirects: {
Query: {
getProject: (_, args, obj) => {
console.log('Hello world');
},
},
}
});
The above cache redirect is never hit. However, if I modify it to look like:
const cache = new InMemoryCache({
dataIdFromObject: ({ id }) => id,
cacheRedirects: {
Query: {
homework: (_, args, obj) => {
console.log('Hello world');
},
},
}
});
It does get hit, however I don't have any of the arguments that are passed in the nested getProject query. What's also confusing is that this cache redirect function is hit for queries that it seemingly shouldn't get hit for, like:
export const SESSION = gql`
query Session {
session {
user {
id
fullName
email
}
organizations {
name
id
}
}
}
`;
So what is going on? I've resorted to just using readFragment in the places where I want the cache to redirect, but I'd like for that logic to become centralized.
It's hard to say for sure with these kinds of issues, but I'm betting that, since you say
What's also confusing is that this cache redirect function is hit for queries that it seemingly shouldn't get hit for
the issue might be with your dataIdFromObject function.
This function is ultimately what decides if data is read from the cache or not. You should only override this if you have a very specific reason to. For example:
import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory';
// ...
export default new ApolloClient({
link,
cache: new InMemoryCache({
dataIdFromObject(object) {
switch (object.__typename) {
case 'ModifierScale':
case 'ModifierGroup':
return [
object.__typename,
object.id,
...object.defaults
.map((defaultModifier) => defaultModifier.id)
.join(''),
].join('');
default:
return defaultDataIdFromObject(object); // fall back to default handling
}
},
}),
});
The point of this setting is to allow you to customize the key that gets put into the cache when you are loading the data.
If this doesn't solve your issue, I would definitely go into the Apollo tab in the chrome dev tools (you need the Apollo dev tools chrome extension to do this) and look at the cache section. It should show you the data in the cache and the key that the data is stored in.

Apollo useQuery() - "refetch" is ignored if the response is the same

I am trying to use Apollo-client to pull my users info and stuck with this problem:
I have this Container component responsible for pulling the user's data (not authentication) once it is rendered. User may be logged in or not, the query returns either viewer = null or viewer = {...usersProps}.
Container makes the request const { data, refetch } = useQuery<Viewer>(VIEWER);, successfully receives the response and saves it in the data property that I use to read .viewer from and set it as my current user.
Then the user can log-out, once they do that I clear the Container's user property setUser(undefined) (not showed in the code below, not important).
The problem occurred when I try to re-login: Call of refetch triggers the graphql http request but since it returns the same data that was returned during the previous initial login - useQuery() ignores it and does not update data. Well, technically there could not be an update, the data is the same. So my code setUser(viewer); does not getting executed for second time and user stucks on the login page.
const { data, refetch } = useQuery<Viewer>(VIEWER);
const viewer = data && data.viewer;
useEffect(() => {
if (viewer) {
setUser(viewer);
}
}, [ viewer ]);
That query with the same response ignore almost makes sense, so I tried different approach, with callbacks:
const { refetch } = useQuery<Viewer>(VIEWER, {
onCompleted: data => {
if (data.viewer) {
setUser(data.viewer);
}
}
});
Here I would totally expect Apollo to call the onCompleted callback, with the same data or not... but it does not do that. So I am kinda stuck with this - how do I make Apollo to react on my query's refetch so I could re-populate user in my Container's state?
This is a scenario where apollo's caches come handy.
Client
import { resolvers, typeDefs } from './resolvers';
let cache = new InMemoryCache()
const client = new ApolloClient({
cache,
link: new HttpLink({
uri: 'http://localhost:4000/graphql',
headers: {
authorization: localStorage.getItem('token'),
},
}),
typeDefs,
resolvers,
});
cache.writeData({
data: {
isLoggedIn: !!localStorage.getItem('token'),
cartItems: [],
},
})
LoginPage
const IS_LOGGED_IN = gql`
query IsUserLoggedIn {
isLoggedIn #client
}
`;
function IsLoggedIn() {
const { data } = useQuery(IS_LOGGED_IN);
return data.isLoggedIn ? <Pages /> : <Login />;
}
onLogin
function Login() {
const { data, refetch } = useQuery(LOGIN_QUERY);
let viewer = data && data.viewer
if (viewer){
localStorage.setItem('token',viewer.token)
}
// rest of the stuff
}
onLogout
onLogout={() => {
client.writeData({ data: { isLoggedIn: false } });
localStorage.clear();
}}
For more information regarding management of local state. Check this out.
Hope this helps!

Returning Promises from Vuex actions

I recently started migrating things from jQ to a more structured framework being VueJS, and I love it!
Conceptually, Vuex has been a bit of a paradigm shift for me, but I'm confident I know what its all about now, and totally get it! But there exist a few little grey areas, mostly from an implementation standpoint.
This one I feel is good by design, but don't know if it contradicts the Vuex cycle of uni-directional data flow.
Basically, is it considered good practice to return a promise(-like) object from an action? I treat these as async wrappers, with states of failure and the like, so seems like a good fit to return a promise. Contrarily mutators just change things, and are the pure structures within a store/module.
actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here... lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator and change something in state
resolve(response); // Let the calling function know that http is done. You may send some data back
}, error => {
// http failed, let the calling function know that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created. Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
console.log("Got some data, now lets show something in this component")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
}
As you can see above, it is highly beneficial for actions to return a Promise. Otherwise there is no way for the action initiator to know what is happening and when things are stable enough to show something on the user interface.
And a last note regarding mutators - as you rightly pointed out, they are synchronous. They change stuff in the state, and are usually called from actions. There is no need to mix Promises with mutators, as the actions handle that part.
Edit: My views on the Vuex cycle of uni-directional data flow:
If you access data like this.$store.state["your data key"] in your components, then the data flow is uni-directional.
The promise from action is only to let the component know that action is complete.
The component may either take data from promise resolve function in the above example (not uni-directional, therefore not recommended), or directly from $store.state["your data key"] which is unidirectional and follows the vuex data lifecycle.
The above paragraph assumes your mutator uses Vue.set(state, "your data key", http_data), once the http call is completed in your action.
Just for an information on a closed topic:
you don’t have to create a promise, axios returns one itself:
Ref: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4
Example:
export const loginForm = ({ commit }, data) => {
return axios
.post('http://localhost:8000/api/login', data)
.then((response) => {
commit('logUserIn', response.data);
})
.catch((error) => {
commit('unAuthorisedUser', { error:error.response.data });
})
}
Another example:
addEmployee({ commit, state }) {
return insertEmployee(state.employee)
.then(result => {
commit('setEmployee', result.data);
return result.data; // resolve
})
.catch(err => {
throw err.response.data; // reject
})
}
Another example with async-await
async getUser({ commit }) {
try {
const currentUser = await axios.get('/user/current')
commit('setUser', currentUser)
return currentUser
} catch (err) {
commit('setUser', null)
throw 'Unable to fetch current user'
}
},
Actions
ADD_PRODUCT : (context,product) => {
return Axios.post(uri, product).then((response) => {
if (response.status === 'success') {
context.commit('SET_PRODUCT',response.data.data)
}
return response.data
});
});
Component
this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
if (res.status === 'success') {
// write your success actions here....
} else {
// write your error actions here...
}
})
TL:DR; return promises from you actions only when necessary, but DRY chaining the same actions.
For a long time I also though that returning actions contradicts the Vuex cycle of uni-directional data flow.
But, there are EDGE CASES where returning a promise from your actions might be "necessary".
Imagine a situation where an action can be triggered from 2 different components, and each handles the failure case differently.
In that case, one would need to pass the caller component as a parameter to set different flags in the store.
Dumb example
Page where the user can edit the username in navbar and in /profile page (which contains the navbar). Both trigger an action "change username", which is asynchronous.
If the promise fails, the page should only display an error in the component the user was trying to change the username from.
Of course it is a dumb example, but I don't see a way to solve this issue without duplicating code and making the same call in 2 different actions.
actions.js
const axios = require('axios');
const types = require('./types');
export const actions = {
GET_CONTENT({commit}){
axios.get(`${URL}`)
.then(doc =>{
const content = doc.data;
commit(types.SET_CONTENT , content);
setTimeout(() =>{
commit(types.IS_LOADING , false);
} , 1000);
}).catch(err =>{
console.log(err);
});
},
}
home.vue
<script>
import {value , onCreated} from "vue-function-api";
import {useState, useStore} from "#u3u/vue-hooks";
export default {
name: 'home',
setup(){
const store = useStore();
const state = {
...useState(["content" , "isLoading"])
};
onCreated(() =>{
store.value.dispatch("GET_CONTENT" );
});
return{
...state,
}
}
};
</script>

parse cloud code query in for loop

I have a table called friends with fields
userid and friendid.
I want to query the database to find a user's friends. This is working fine by using the following code:
Parse.Cloud.define("searchfriend", function(request, response) {
var query = new Parse.Query("friends");
query.equalTo("player", request.params.myid);
query.find({
success: function(results) {
var listfreundids = [];
for (var i = 0; i < results.length; ++i) {
listfreundids[i] = results[i].get("friend");;
}
response.success(listfreundids);
},
error: function() {
response.error("error");
}
});
});
Now I have the problem to find the username matching the friendid because i cannot use a 2nd query within the for loop to query the user database...
Using promises you can split this up into several separate parts. Promises are really awesome to use and really easy to create your own promises too.
What I would do is split this up into a query that finds the friend ids and then a query that finds the friends...
Parse.Cloud.define("searchfriend", function(request, response) {
getFriendIDs(request.params.myid).then(function(friendIDs) {
return getFriendUserNames(friendIDs);
}).then(function(friends) {
response.success(friends);
}), function(error) {
response.error(error);
});
});
// function to get the IDs of friends
function getFriendIDs(myID) {
var promise = new Parse.Promise();
var query = new Parse.Query("friends");
query.equalTo("player", myID);
query.find().then(function(friendIDs) {
promise.resolve(friendIDs);
}, function(error) {
promise.reject(error);
});
return promise;
}
// function to get the friends from a list of IDs
function getFriendUserNames(friendIDs) {
var promise = new Parse.Promise();
var query = new Parse.Query("_User");
query.containedIn("id", friendIDs);
query.find().then(function(friends) {
// here I am just returning the array of friends
// but you can pull the names out if you want.
promise.resolve(friends);
}, function(error) {
promise.reject(error);
});
return promise;
}
You could always user a matches query too...
// function to get friends
function getFriends(myID) {
var promise = new Parse.Promise();
var friendQuery = new Parse.Query("friends");
friendQuery.equalTo("player", myID);
var userQuery = new Parse.Query("User");
userQuery.matchesKeyInQuery("ID", "friendID", friendQuery);
userQuery.find().then(function(friends) {
promise.resolve(friends);
}, function(error) {
promise.reject(error);
});
return promise;
}
This will perform a joined query where it gets the friend IDs first and then uses the friend ID to get the user and returns the user object.
Also, use promises. They are much easier to work with and can be pulled apart into separate working units.
Of course, I have no idea if the syntax here is correct and what the correct names should be or even what your object model looks like but hopefully this can act as a guide.
You do not have to query for each friend id that you have found (inefficient). Instead, after getting the list of friend ids, you can query the user database via sending the list of friend ids with the query.containedIn constraints. This strategy will actually decrease the number of query count.Based on retrieved result you can get friend (previously found) information. One more thing to remember, call response success after the operations are executed.Hope this helps.
Regards.

Backbone fetching data using a callback cursor

How would I use Backbones fetch to deal with callback results that contain a cursor? I'm going to use this simple example of a book that is fetching pages.
var Book = Backbone.Collection.extend({
model: Page,
recursiveFetch: function(cursor) {
this.fetch({
url: 'book/pages',
data: {
cursor: {cursor here};
},
success: function(response) {
if (response.cursor) {
this.recursiveFetch(response.cursor);
}
}
});
}
})
I need to be able to use fetch to keep fetching until the response doesn't contain a cursor. It should keep adding page models, but not replacing and overwriting them. It needs to do something like the example above, though I'm not sure of the best way to implement it.
I think that all you need to do is add in a {remove: false} into your fetch options. Its also worth mentioning that the this context of your success function may not be the collection, so you might want to pass it into the success function as a parameter. The end result would be:
recursiveFetch: function(cursor) {
this.fetch({
remove:false, // prevents removal of existing models
url: 'book/pages',
success: function(collection, response) {
if (response.cursor) {
collection.recursiveFetch(response.cursor);
}
}
});
}
The fix is very simple: add cursor to the parameters only when it's present. In other cases (i.e. the first time) make a normal request with the rest of the parameters.
var CursorCollection = Backbone.Collection.extend({
fetchAll: function(cursor) {
var params = {
// if you have some other request parameters...
};
// if we have a cursor from the previous call, add it to the parameters
if (cursor) { params.cursor = cursor; }
this.fetch({
remove: false,
data: params,
success: function(collection, response) {
if (response.cursor) {
return collection.fetchAll(response.cursor);
}
}
});
}
});
Then the first time you call it collection.fetchAll() and it recurses until it gets a response without a cursor.
Note, that the remove: false parameter is very important to accumulate the results as pointed out by #dcarson.

Resources