Apollo client useMutation in expo renders twice for every call - expo

I have a basic expo app with React Navigation.
In the top function Navigation I am initiating a useMutation call to an Apollo server like so:
import { callToServer, useMutation } from '../graphQL';
function Navigation() {
console.log("RENDERED");
const [call] = useMutation(callToServer);
call({ variables: { uid: 'xyz', phoneNumber: '123' } });
...
And my GraphQL settings is as follows:
import {
ApolloClient,
createHttpLink,
InMemoryCache,
useMutation,
} from '#apollo/client';
import { onError } from '#apollo/client/link/error';
import { callToServer } from './authAPI';
const cache = new InMemoryCache();
const httpLink = createHttpLink({
uri: `XXXXXXX/my-app/us-central1/graphql`,
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
});
const client = new ApolloClient({
cache,
link: errorLink.concat(httpLink),
});
export {
useMutation,
callToServer,
};
export default client;
I want to clarify that I removed the httpLink from the client setting and I still get the two renders per call. I can see in the console that console.log("RENDERED") prints three times. Once when the app loads (normal) and twice after the useMutation call (not normal?)
What's going on here? Why is react re-renders twice per useMutation call? How do I avoid it?
UPDATE
I did further digging and it seems that useMutation does indeed cause the App to render twice - once when the request is sent, and once when it receives a response. I'm not sure I'm loving this default behavior which seems to have no way to disable. Why not let us decide if we want to re-render the App?
If someone has more insight to offer, Id love to hear about it.

Probably it's too late and maybe you've already found the solution, but still...
As I see you do not need data returned from mutation in the code above. In this case you can use useMutation option "ignoreResults" and set it to "true". So mutation will not update "data" property and will not cause any render.

Related

How to use Pinia outside a component in js file

I am migrating from vue 4.x to pinia, one of my file needs api key from store.
But I can't make it work even though I follow the Pinia documentation .
here is how I use pinia
// Repository.ts
import axios from "axios";
import { createPinia } from 'pinia'
import { useAuthStore } from '../stores/auth-store'
const pinia=createPinia();
let authStore = useAuthStore(pinia);
const baseURL = 'http://127.0.0.1:5678/res-api';
export default axios.create({
baseURL,
headers:{"Authorization":"Bearer " + authStore.getToken,
"Accept":"application/json"},
});
Expected result : to get the token from the store.
Console error
Uncaught ReferenceError: Cannot access 'useAuthStore' before initialization
at Repository.ts:6:17
Note: this working inside a component
You can solve this by importing the store inside the interceptors
import axios from "axios";
import { useAuthStore } from '../stores/auth-store';
const axiosClient = axios.create({
baseURL: 'http://127.0.0.1:5678/res-api'
});
axiosClient.interceptors.request.use((config) => {
const authStore = useAuthStore();
config.headers.Authorization = `Bearer ${authStore.getToken}`;
config.headers.Accept = "application/json";
return config
})
export default axiosClient;
This discussion may help you: Go to GitHub discussion
According to the documentation the pinia you created must go as a parameter to app.use. Not only that, but useAuthStore must be a store defined with defineStore and must not take a parameter. I'll leave a link that can help you, it doesn't create the store but you can browse the side menu to see several examples.
https://pinia.vuejs.org/core-concepts/outside-component-usage.html
Here is my sample project to demo the issue: https://codesandbox.io/s/infallible-shamir-sxrlb9.
The main cause here is that you cannot use Pinia's stores before passing it to the Vue's app. So given following code:
const pinia = createPinia(); // line 1
createApp(App).use(pinia).mount("#app"); // line 2
You cannot trigger any store in between line 1 and 2, but only after line 2.
In your code, likely you trigger an axios call before creating Vue app/add Pinia to Vue app. Please try to delay that axios call to trigger after Vue app's setup is complete.

Ember Super Rentals Tutorial 3.15 - Working with data

I was following the ember Super Rental 3.15 tutorial, when I got to the working with data section, I updated the route index file with model hooks, the page stopped working. Also I am finding ember tutorials to be incomplete.
error says property of map is undefined
code in routes index.js file:
import Route from '#ember/routing/route';
const COMMUNITY_CATEGORIES = [
'Condo',
'Townhouse',
'Apartment'
];
export default class IndexRoute extends Route {
async model() {
let response = await fetch('/api/rentals.json');
let { data } = await response.json();
return data.map(model => {
let { attributes } = model;
let type;
if (COMMUNITY_CATEGORIES.includes(attributes.category)) {
type = 'Community';
} else {
type = 'Standalone';
}
return { type, ...attributes };
});
}
}
image if error message:
Your problem is that fetch('/api/rentals.json'); does not return the correct data. And so when you do let { data } = await response.json(); then data will be undefined and you can not do undefined.map.
So the code you posted is correct. The problem is somewhere else. You can check:
did you correctly add the rentals.json file? If you open http://localhost:4200/api/rentals.json do you see the data? So have you done this?
I see some error from mirage. The super-rentals tutorial does not use mirage. I can see this here (sidenote: that git repo is automatically created from the guides, so its always up to date). So this could be your problem. Depending how you configure mirage it will basically mock all your ajax requests. This means that fetch(... will no longer work then expected, mirage assumes you always want to use mocked data and you did not configure mirage correctly. You can try to remove mirage from your package.json, rerun npm install, restart the ember server and try it again.

How to access Ember Data store when registering test helper? Ember 3.3

I'm using a custom test helper which requires access to the Ember data store, but I don't know how to access it from the given application argument.
export default registerAsyncHelper('myCustomHelper', function(app) {
console.log(app); // how to access store?
let store = app.__registry__.registrations['service:store'];
store.pushPayload(// json payload);
});
How can I get access to the store when registering a custom helper? I've been trying to figure out a way to access it from the __registry__.registrations['service:store'] key but that gives me an undefined value, when I can see that it's there and has the pushPayload function. Help would be greatly appreciated
Hah! I think I got it:
export default registerAsyncHelper('myCustomHelper', function(app) {
let instance = app.buildInstance();
let store = instance.lookup('service:store');
store.pushPayload(// json payload);
});
Not sure if that has any side effects though? Please let me know if it does, I think I've spent enough time trying to setup a good test environment already :p
This is typescript, but it should hopefully work the same in js (without the type annonations though)
// tests/helpers/get-service.ts
import { getContext } from "#ember/test-helpers";
export function getService<T>(name: string): T {
const { owner } = getContext();
const service = owner.lookup(`service:${name}`);
return service;
}
example usage:
// tests/helpers/create-current-user.ts
import { run } from '#ember/runloop';
import { DS } from 'ember-data';
import Identity from 'emberclear/data/models/identity/model';
import { getService } from './get-service';
export async function createCurrentUser(): Promise<Identity> {
const store = getService<DS.Store>('store');
const record = store.createRecord('identity', {
id: 'me', name: 'Test User'
});
await record.save();
return record;
}
this code is from https://emberclear.io
https://gitlab.com/NullVoxPopuli/emberclear/tree/master/packages/frontend/tests/helpers
hope this helps :)

How to test VueRouter's beforeRouteEnter using '#vue/test-utils'?

I'm trying to test my 'Container' component which handles a forms logic. It is using vue-router and the vuex store to dispatch actions to get a forms details.
I have the following unit code which isn't working as intended:
it('On route enter, it should dispatch an action to fetch form details', () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
wrapper.vm.$options.beforeRouteEnter[0]();
expect(getFormDetails.called).to.be.true;
});
With the following component (stripped of everything because I don't think its relevant (hopefully):
export default {
async beforeRouteEnter(to, from, next) {
await store.dispatch('getFormDetails');
next();
}
};
I get the following assertion error:
AssertionError: expected false to be true
I'm guessing it is because I am not mounting the router in my test along with a localVue. I tried following the steps but I couldn't seem to get it to invoke the beforeRouteEnter.
Ideally, I would love to inject the router with a starting path and have different tests on route changes. For my use case, I would like to inject different props/dispatch different actions based on the component based on the path of the router.
I'm very new to Vue, so apologies if I'm missing something super obvious and thank you in advance for any help! 🙇🏽
See this doc: https://lmiller1990.github.io/vue-testing-handbook/vue-router.html#component-guards
Based on the doc, your test should look like this:
it('On route enter, it should dispatch an action to fetch form details', async () => {
const getFormDetails = sinon.stub();
const store = new Vuex.Store({
actions: { getFormDetails }
});
const wrapper = shallowMount(MyComponent, { store });
const next = sinon.stub()
MyComponent.beforeRouteEnter.call(wrapper.vm, undefined, undefined, next)
await wrapper.vm.$nextTick()
expect(getFormDetails.called).to.be.true;
expect(next.called).to.be.true
});
A common pattern with beforeRouteEnter is to call methods directly at the instantiated vm instance. The documentation states:
The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component instance via `vm`
})
}
This is why simply creating a stub or mock callback of next does not work in this case. I solved the problem by using the following parameter for next:
// mount the component
const wrapper = mount(Component, {});
// call the navigation guard manually
Component.beforeRouteEnter.call(wrapper.vm, undefined, undefined, (c) => c(wrapper.vm));
// await
await wrapper.vm.$nextTick();

Ember-simple-auth: Use Custom cookie session to store additional token

Using: ember-cli v2.5, ember-simple-auth v1.1.0
I got trouble understanding if I can properly store additional token inside a custom ember-simple-auth's cookie based session-store.
I'm trying to store a shopping-cart token to be sure it survives browser refresh.
I started to create a shopping-cart service to handle init, add, remove etc ... regarding if the session has a token.
Here is my app/session-stores/application.js
// app/session-stores/application.js
import Cookie from 'ember-simple-auth/session-stores/cookie';
export default Cookie.extend({
orderToken: null
});
Doesn't seems to be used. The session service still use the adaptive store.
Here is my shopping-cart service
// app/services/shopping-cart.js
import Ember from 'ember';
export default Ember.Service.extend({
store: Ember.inject.service('store'),
session: Ember.inject.service('session'),
basket: null,
[...]
init() {
this._super(...arguments);
let orderToken = this.get('session.orderToken'); // First try
// let orderToken = this.get('session.store.orderToken'); // Second Try
if (orderToken) {
this.get('store').findRecord('order', orderToken).then((order) => {
this.set('basket', order);
})
}
},
[...]
_createBasket() {
return this.get('store').createRecord('order', {}).save().then((order) => {
this.set('basket', order);
this.set('session.orderToken', order.token); // First try
// this.set('session.store.orderToken', order.token); // Second Try
});
}
})
Then the idea will be to inject the service wherever I need. Unfortunately, It doesn't work, and I don't really know if I can do it or if it's the right way to do it.
Any advices, answers will be much appreciate !
I am currently using ember-cookie and it's working like a charm. I am juste trying to play with ember-simple-auth and understand all my possibilities.