How to use Apollo writeQuery in client side ? (Nuxt.js) - vue.js

I have a Nuxt.js app.
How to use Apollo writeQuery method on client side without using mutation?
I want when I click the button (bind to transfer method), all products (on backend) to be saved on client server (frontend) cache, and never query the main server.
When a new person visit the page for first time, I don't want any query to be sent to the backend server and always use frontend nodejs cache. Even for first visit.
For the backend I use WP-GraphQL
When I use:
import gql from 'graphql-tag';
const getProducts = gql`
query getProducts {
products {
nodes {
name
id
}
}
}
`
export default{
methods: {
async transfer() {
const { data } = await this.$apollo.query({
query: getProducts,
fetchPolicy: 'network-only',
})
await this.$apollo.writeQuery({
query: getProducts,
data,
})
},
},
async asyncData({ app }) {
let client = app.apolloProvider.defaultClient
const { data } = await client.query({
query: getProducts,
fetchPolicy: 'cache-only',
})
return { products: data }
},
}
It returns error:
TypeError: _this.$apollo.writeQuery is not a function

Related

How to access object on Vue front end from axios proxy

I have a locally hosted mongodb database with mongoose, express, axios, and a Vue front end. Right now I'm trying to access a single object from an exported array, but I'm missing the mark and getting "undefined" as the result.
vue.config.js:
module.exports = {
devServer: {
proxy: 'http://localhost:3000',
}
}
here's the front end Vue script meant to use the objects:
import axios from 'axios';
export default {
name: 'Game',
data () {
return {
pages: [],
currentPage: {},
pageTitle: "",
pageText: "",
options: [],
}
},
created () {
this.getPages();
},
methods: {
async getPages() {
try {
let res = await axios.get('/api/pages');
this.pages = res.data;
console.log(this.pages);
this.currentPage = this.pages[0];
console.log(this.currentPage);
return true;
} catch (error) {
console.log(error);
}
},
my "get" endpoint in pages.js:
router.get('/', async (req, res) => {
try {
let pages = await Page.find();
res.send({pages: pages}); //send result of search for pages as list of pages called "pages"
} catch (error) {
console.log(error);
res.sendStatus(500); //500 = server could not fulfill request
}
});
the route in server.js:
const pages = require('./routes/pages');
app.use('/api/pages', pages);
app.listen(3000, () => console.log('Server listening on port 3000!'));
module.exports = app;
and here's the console output, with the "pages" object from vue's data property and the "currentPage" that's supposed to be at pages[0] (printed to console in earlier example):
I can access the api at 'localhost:3000/api/pages' just fine, but how do I break into that array and access the first page object? I want to get an object from the list axios fetches from mongoose, then hold that object in a variable so I can access it's properties. The whole "pages > [[Target]] > pages > [ ]" is part of the problem I'm sure, but I don't know what to tell the code to open it.
Whoops! I realized my mistake. In pages.js I should have sent "res.send(pages);" After a whole couple days too XD

How to get rid of CORS error in Nuxt/SSR?

So, I made a simple api request using Nuxt/SSR. I have cities and categories. Each city has a list of categories. To fetch cities I have to use https://api/cities and to get categories I should use https://api/cities?id
store/cities.js
export const state = () => ({
cities: [],
})
export const mutations = {
setCities(state, cities) {
state.cities = cities
},
}
export const actions = {
async fetch({ commit }) {
const cities = await this.$axios.$get(
'https://api/cities'
)
commit('setCities', cities)
},
}
export const getters = {
cities: (s) => s.cities,
}
And it works perfectly with this index.vue:
export default Vue.extend({
async fetch({ store }) {
if (store.getters['cities/cities'].length === 0) {
await store.dispatch('cities/fetch')
}
},
computed: {
cities() {
return this.$store.getters['cities/cities']
}
}
})
But now I want to fetch categories on click (to choose city ID):
store/categories.js
export const state = () => ({
categories: [],
})
export const mutations = {
setCategories(state, categories) {
state.categories = categories
},
}
export const actions = {
async fetch({ commit }, cityId) {
const categories = await this.$axios.$get(
'https://api/cities?id=' + <data that I get on click>
)
commit('setCategories', categories)
},
}
export const getters = {
categories: (s) => s.categories,
}
And index.vue now:
export default Vue.extend({
computed: {
cityCategories() {
return []
},
},
methods: {
// this function I call on click
selectCity(selectedCityId) {
this.$store.dispatch('categories/fetch', selectedCityId)
}
}
})
But after this I get an error on click:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api/cities?id. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api/cities?id. (Reason: CORS request did not succeed). Status code: (null).
I'm sure the problem isn't in api, because I tried to fetch categories with 1st method without click (just edited id to 1) and it worked. I have the same local domain, so it's not about headers in api.
It didn't work on click because it was a request from browser/localhost, not from nuxt server. So I just installed nuxtjs proxy to send a request on local server first.
CORS is definitely at the API.
Set the Access-Control-Allow-Origin header on response from the back-end.

Vue3 / Vuex State is empty when dispatching action inside of lifecycle hook inside of test

We're using the composition API with Vue 3.
We have a Vuex store that, amongst other things, stores the currentUser.
The currentUser can be null or an object { id: 'user-uuid' }.
We're using Vue Test Utils, and they've documented how to use the store inside of tests when using the Composition API. We're using the store without an injection key, and so they document to do it like so:
import { createStore } from 'vuex'
const store = createStore({
// ...
})
const wrapper = mount(App, {
global: {
provide: {
store: store
},
},
})
I have a component and before it is mounted I want to check if I have an access token and no user currently in the store.
If this is the case, we want to fetch the current user (which is an action).
This looks like so:
setup() {
const tokenService = new TokenService();
const store = useStore();
onBeforeMount(async () => {
if (tokenService.getAccessToken() && !store.state.currentUser) {
await store.dispatch(FETCH_CURRENT_USER);
console.log('User: ', store.state.currentUser);
}
});
}
I then have a test for this that looks like this:
it('should fetch the current user if there is an access token and user does not exist', async () => {
localStorage.setItem('access_token', 'le-token');
await shallowMount(App, {
global: {
provide: {
store
}
}
});
expect(store.state.currentUser).toStrictEqual({ id: 'user-uuid' });
});
The test fails, but interestingly, the console log of the currentUser in state is not empty:
console.log src/App.vue:27
User: { id: 'user-uuid' }
Error: expect(received).toStrictEqual(expected) // deep equality
Expected: {"id": "user-uuid"} Received: null
Despite the test failure, this works in the browser correctly.
Interestingly, if I extract the logic to a method on the component and then call that from within the onBeforeMount hook and use the method in my test, it passes:
setup() {
const tokenService = new TokenService();
const store = useStore();
const rehydrateUserState = async () => {
if (tokenService.getAccessToken() && !store.state.currentUser) {
await store.dispatch(FETCH_CURRENT_USER);
console.log('User: ', store.state.currentUser);
}
};
onBeforeMount(async () => {
await rehydrateUserState();
});
return {
rehydrateUserState
};
}
it('should fetch the current user if there is an access token and user does not exist', async () => {
localStorage.setItem('access_token', 'le-token');
await cmp.vm.rehydrateUserState();
expect(store.state.currentUser).toStrictEqual({ id: 'user-uuid' });
});
Any ideas on why this works when extracted to a method but not when inlined into the onBeforeMount hook?

Nuxt.js - The best place for API calls

I'm new to Vue.js Nuxt and all front-end stuff.
I have a question about API calls. I'm not sure what is the right way, the best practice here.
I have a store. In that store, I have actions that are calling my API and sets state eg.
async fetchArticle({ state, commit }, uuid) {
const response = await this.$axios.get(`articles/${uuid}/`)
commit('SET_ARTICLE', response.data)
},
And that is fine it is working for one component.
But what if I want to just fetch the article and not changing the state.
To be DRY first thing that comes to my mind is to create the service layer that is fetching the data and is used where it is needed.
Is it the right approach? Where can I find some real-world examples that I can take inspiration from?
Using the repository pattern to abstract your API is definitely a good idea! Whether you use the #nuxtjs/axios module or the #nuxt/http module, you can pass either instance to your repository class/function. Below a real world example of an abstracted "repository.js" file.
export default $axios => resource => ({
index() {
return $axios.$get(`/${resource}`)
},
create(payload) {
return $axios.$post(`/${resource}`, payload)
},
show(id) {
return $axios.$get(`/${resource}/${id}`)
},
update(payload, id) {
return $axios.$put(`/${resource}/${id}`, payload)
},
delete(id) {
return $axios.$delete(`/${resource}/${id}`)
}
})
You can then create a plugin to initialize all different kinds of repositories for your endpoints:
import createRepository from '~/path/to/repository.js'
export default (ctx, inject) => {
const repositoryWithAxios = createRepository(ctx.$axios)
const repositories = {
posts: repositoryWithAxios('posts'),
users: repositoryWithAxios('users')
//...
}
inject('repositories', repositories)
}
Further read: Organize and decouple your API calls in Nuxt.js
I will an example of a service layer implementation for my portfolio to create my dashboard that shows some statics about my github and stackoverflow profiles, to do this i created a folder called services inside the project root :
pages
services
|_AxiosConfig.js
|_GitHubService.js
|_StackoverflowService.js
...
in the AxiosConfig.js file i put i created an axios instance with its configuration :
import axios from 'axios';
const clientAPI = url =>
axios.create({
baseURL: url,
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
export default clientAPI;
then in my GitHubService.js i imported that axios instance called clientAPI which i used to my requests :
import clientAPI from './AxiosConfig';
const baseURL = 'https://api.github.com';
export default {
getUser(name) {
return clientAPI(baseURL).get('/users/' + name);
},
getRepos(name){
return clientAPI(baseURL).get('/users/' + name+'/repos');
},
getEvents(name,page){
return clientAPI(baseURL).get('/users/' + name+'/events?per_page=100&page='+page);
},
getLastYearCommits(name,repo){
return clientAPI(baseURL).get('/repos/' + name+'/'+repo+'/stats/commit_activity');
}
};
then in my page i used asyncData hook to fetch my data :
import GitHubService from '../../services/GitHubService'
export default {
...
async asyncData({ error }) {
try {
const { data } = await GitHubService.getUser("boussadjra");
const resRepos = await GitHubService.getRepos("boussadjra");
return {
user: data,
repos: resRepos.data
};
} catch (e) {
error({
statusCode: 503,
message: "We cannot find the user"
});
}
}
I wanted to use axios in my service/service.js file, so instead of passing axios, I accessed it directly like this:
export default {
async fetchArticle() {
let response = await $nuxt.$axios.$get('/api-url')
return response
},
}
In Nuxt, if you want to just get the data without keeping it in your store, you could use the asyncData function, which asynchronously loads data (from API calls and the like) and pushes it into the component's data object before rendering.

NuxtJS - Prevent fetch if data already exists in state?

I have a portfolio site built using NuxtJS and a headless Wordpress CMS. On several pages, I'm importing a mixin that looks like this:
import { mapActions, mapState } from 'vuex';
export default {
computed: {
...mapState({
galleries: state => state.portfolio.galleries[0],
})
},
methods: {
...mapActions('portfolio', ['fetchGalleries']),
},
async fetch() {
await this.fetchGalleries();
}
}
The Vuex module looks like this:
export const state = () => ({
galleries: [],
});
export const actions = {
async fetchGalleries({ commit }) {
let res = await this.$axios.$get(`${process.env.WP_API_URL}/wp/v2/media`);
const data = res.reduce((acc, item) => {
const { slug } = item.acf.category;
(acc[slug] || (acc[slug] = [])).push(item);
return acc;
}, {});
commit('setGalleries', data);
}
};
export const mutations = {
setGalleries(state, data) {
state.galleries.push(data);
}
};
fetch is being used in the mixin to return data from the api before page load. I noticed however that each time I navigate to a new page, it's running that same fetch and continually adding duplicate data to Vuex state.
How do I prevent fetch from running and continually adding duplicate data to my state if it already exists?
I'm not sure why this was tripping me up so much, but I figured out a very simple solution.
async fetch() {
if (this.galleries.length) return;
await this.fetchGalleries();
}
Just added a conditional return statement as the first line within the fetch function.