Vue content modified after serverPrefetch on client side, when using SSR - vue.js

I am working with Vue, by means of Quasar, with the pages being rendered via SSR. This works well enough, but I have a component that doesn't seem to behaving properly.
The issue is that the content is rendered correctly on the server side (verified by checking network log in Chrome), with the axios call loading in the data into an element using v-html, but when we get to the browser the state seems to be reset and server side rendered content gets lost, when using the 'elements' tab in the inspector.
Any ideas?
The Vue component is as follows:
<template>
<div class="dy-svg" v-html="svgData"></div>
</template>
<script>
/**
* This provides a way of loading an SVG and embedding it straight into
* the page, so that it can have css applied to it. Note, since we are
* using XHR to load the SVG, any non-local resource will have to deal
* with CORS.
*/
import axios from 'axios';
export default {
props: {
src: String,
prefetch: {
type: Boolean,
default: true
}
},
data() {
return {
svgData: undefined,
};
},
async serverPrefetch() {
if (this.prefetch) {
await this.loadImage();
}
},
async mounted() {
// if (!this.svgData) {
// await this.loadImage();
// }
},
methods: {
async loadImage() {
try {
let url = this.src;
if (url && url.startsWith('/')) {
url = this.$appConfig.baseUrl + url;
}
const response = await axios.get(url);
let data = response.data;
const idx = data.indexOf('<svg');
if (idx > -1) {
data = data.substring(idx, data.length);
}
this.svgData = data;
} catch (error) {
console.error(error);
}
}
}
};
</script>
Note, I did try add the v-once attribute to the div, but it seems to have no impact.
Environment:
Quasar 1.1.0
#quasar/cli 1.0.0
#quasar/app 1.0.6
NodeJS 10.15.3
Vue 2.6.10 (dependency via Quasar)

The fetched data needs to live outside the view components, in a dedicated data store, or a "state container". On the server, you should pre-fetch and fill data into the store while rendering. For this you can use Vuex.
Example Vuex store file:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
// import example from './module-example'
Vue.use(Vuex)
export default function ( /* { ssrContext } */ ) {
const Store = new Vuex.Store({
state: () => ({
entities: {}
}),
actions: {
async get({
commit
}) {
await axios.get('https://example.com/api/items')
.then((res) => {
if (res.status === 200) {
commit('set', res.data.data)
}
})
}
},
mutations: {
set(state, entities) {
state.entities = entities
},
},
modules: {},
// enable strict mode (adds overhead!)
// for dev mode only
strict: process.env.DEV
})
return Store
}
Example Vue page script:
export default {
name: 'PageIndex',
computed: {
// display the item from store state.
entities: {
get() {
return this.$store.state.entities
}
}
},
serverPrefetch() {
return this.fetchItem()
},
mounted() {
if (!this.entities) {
this.fetchItem()
}
},
methods: {
fetchItem() {
return this.$store.dispatch('get')
}
}
}
This should solve the issue you're facing.

Related

Vue JS. Layered calls not synchronised: Web Page -> VUEX-> API call

I have refactored my VUE JS code to have a dedicated API layer (Calls out to AWS Graphql services), it is called by the VUEX layer. It now has the following levels:
Web Page -> Vuex -> API
I want to retrieve data (this.getActivities) before referencing it (Point 7). I have cut down the code for simplicity:
async created() {
console.log("Point 1")
await this.getActivities();
},
mounted() {
console.log("Point 7")
// reference the data set by this.getActivities()
},
methods: {
async getActivities() {
// from DB
console.log("Point 2")
this.$store.dispatch('getAllActivities') // vuex call
},
VUEX DATA STORE
actions: {
async getAllActivities ({ commit }) {
console.log("point 3")
const activities = await queries.getActivities()
console.log("point 6")
commit('setActivities', activities)
},
API
async getActivities () {
await API.graphql({
query: listActivities
}).then((response) => {
console.log("Point 4")
})
console.log("Point 5")
return activitiesList
},
Prints the following:
Point 1
Point 2
point 3
Point 7
Point 8
Point 4
Point 5
point 6
I presume I have misused the await/sync processes?
Thanks
Assuming that you need the list of activities in more than 1 component/route (otherwise why would you store this list in Vuex instead of the component itself ?!) you would normally do something like this:
<template>
<div>
<ActivityItem v-for="act in ACTIVITY_LIST" :key="act.id" :activity="act" />
</div>
</template>
<script>
import ActivityItem from './components/ActivityItem';
import { mapGetters, mapActions } from 'vuex';
import { ACTIVITY_LIST, FETCH_ACTIVITIES } from './store/constants';
export default
{
components:
{
ActivityItem,
},
computed:
{
...mapGetters([ACTIVITY_LIST]),
},
created()
{
this[FECH_ACTIVITIES]();
},
methods:
{
...mapActions([FETCH_ACTIVITIES])
}
}
</script>
// store/constants.js
export const ACTIVITY_LIST = 'ACTIVITY_LIST';
export const FETCH_ACTIVITIES = 'FETCH_ACTIVITIES';
export const SET_ACTIVITIES = 'SET_ACTIVITIES';
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import { ACTIVITY_LIST, FETCH_ACTIVITIES, SET_ACTIVITIES } from './store/constants';
import myAPI from './api';
Vue.use(Vuex);
export default new Vuex.Store(
{
strict: process.env.NODE_ENV !== 'production',
state()
{
return {
activities: []
};
},
getters:
{
[ACTIVITY_LIST](state)
{
return state.activities;
}
},
mutations:
{
[SET_ACTIVITIES](state, value)
{
state.activities = value || [];
}
},
actions:
{
[FETCH_ACTIVITIES]({ commit })
{
return myAPI.getActivities().then(response =>
{
commit(SET_ACTIVITIES, response.data.activitiesList);
return response.data.activitiesList; // optional
});
}
}
});

How to properly use Vuex getters in Nuxt Vue Composition API?

I use #nuxtjs/composition-api(0.15.1), but I faced some problems about accessing Vuex getters in computed().
This is my code in composition API:
import { computed, useContext, useFetch, reactive } from '#nuxtjs/composition-api';
setup() {
const { store } = useContext();
const products = computed(() => {
return store.getters['products/pageProducts'];
});
const pagination = computed(() => {
return store.getters['products/pagination'];
});
useFetch(() => {
if (!process.server) {
store.dispatch('products/getPage');
}
});
return {
products,
pagination,
};
}
And the console keeps reporting the warning:
[Vue warn]: Write operation failed: computed value is readonly.
found in
---> <Pages/products/Cat.vue> at pages/products/_cat.vue
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
I'm really confused. Because I didn't try to mutate the computed property, just fetching the Data with the AJAX and then simply assign the data to the state in the Vuex mutations.
But I rewrite the code in option API in this way:
export default {
components: {
ProductCard,
Pagination,
},
async fetch() {
if (process.server) {
await this.$store.dispatch('products/getPage');
}
},
computed: {
products() {
return this.$store.getters['products/pageProducts'];
},
pagination() {
return this.$store.getters['products/pagination'];
},
},
};
Everything works fine, there's no any errors or warnings. Is it the way I'm wrongly accessing the getters in the composition API or that's just a bug with the #nuxtjs/composition-api plugin?
fix: computed property hydration doesn't work with useFetch #207
This problem might not can be solved until the Nuxt3 come out.
But I found an alternative solution which use the middleware() instead of use useFetch(), if you want to the prevent this bug by fetching AJAX data with Vuex Actions and then retrieve it by Getters via the computed().
I make another clearer example which it's the same context like the question above.
~/pages/index.vue :
<script>
import { computed, onMounted, useContext, useFetch } from '#nuxtjs/composition-api';
export default {
async middleware({ store }) {
await store.dispatch('getUser');
},
setup() {
const { store } = useContext();
const user = computed(() => store.getters.user);
return {
user,
};
},
}
</script>
~/store/index.js (Vuex)
const state = () => ({
user: {},
});
const actions = {
async getUser({ commit }) {
const { data } = await this.$axios.get('https://randomuser.me/api/');
console.log(data.results[0]);
commit('SET_USER', data.results[0]);
},
};
const mutations = {
SET_USER(state, user) {
state.user = user;
},
};
const getters = {
user(state) {
return state.user;
},
};
If there's something wrong in my answer, please feel free to give your comments.

Why is Vuex not detected after refresh page? (nuxt)

Vuex is not detected after refresh, but all data is output to the console. Also after refresh, some components behave incorrectly. For example, I use vee-validate and all the rules and fields I get from the back, after refresh the validation rules disappear, but the fields are displayed
Vuex works on all pages but after refresh only on the home page
stroe/index.js
export const state = () => ({});
const map = {
ru: "ru",
uk: "uk-ua"
};
export const getters = {
lang(state) {
return map[state.i18n.locale];
}
};
export const mutations = {};
export const actions = {
async nuxtServerInit({ state, dispatch }) {
try {
await dispatch('category/getCategories', {
});
} catch (err) {
console.log('nuxt server init error', err);
}
}
};
home page (everything works)
<template>
<div>
<main class="home-page">
<banner />
<section class="home_page">
<div class="container">
<phone-pay />
<card-pay />
<categories :categories="categories" :services="services" />
<main-banner />
</div>
</section>
</main>
</div>
</template>
<script>
import Banner from "#/components/Index/Banner";
import PhonePay from "#/components/Index/PhonePay";
import CardPay from "#/components/Index/CardPay";
import Categories from "#/components/Index/Categories";
import MainBanner from "#/components/Index/MainBanner";
export default {
components: {
Banner,
PhonePay,
CardPay,
Categories,
MainBanner
},
async asyncData({ store, app: { $api }, error, req }) {
try {
const {
data: { data: categories, included: services }
} = await $api.CategoryProvider.getPopularCategories({
params: {
include: "services"
}
});
return {
lang: store.getters.lang,
categories,
services
};
} catch (e) {
console.log("error index", e);
error({ statusCode: 404, message: "Page not found" });
}
}
};
</script>
category (does not work)
<template>
<services-viewer :initial-services="initialServices" :category="category" :init-meta="initMeta" />
</template>
<script>
import ServicesViewer from "#/components/UI/ServicesViewer";
export default {
components: {
ServicesViewer
},
async asyncData({ store, route, error, app: { $api } }) {
try {
const {
data: { data: initialServices, meta: initMeta }
} = await $api.ServiceProvider.getServices({
params: {
"filter[category_slug]": route.params.id,
include: "category"
// "page[size]": serviceConfig.SERVICE_PAGINATION_PAGE_SIZE
}
});
await store.dispatch("category/getCategories", {
params: {}
});
const category = store.state.category.categories.find(
({ attributes: { slug } }) => slug === route.params.id
);
return {
initialServices,
category,
initMeta
};
} catch (e) {
const statusCode = e && e.statusCode ? e.statusCode : 404;
error({ statusCode });
}
}
};
</script>
install the below package:
npm install --save vuex-persistedstate
then change your store like below, then your data will be available after refresh the page.
// store/index.js
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate'
const createStore = () =>
new Vuex.Store({
plugins: [createPersistedState()],
state: {
},
mutations: {
},
getters:{
}
});
export default createStore;
for more details you can read from here.
I solved it. It was my mistake. I have a parallax plugin that works on the home page, but if you go to another page and refresh, the plugin starts and cannot find the item and breaks the page.
follow this link for your question
The nuxtServerInit Action
If the action nuxtServerInit is defined in the store and the mode is universal, Nuxt.js will call it with the context (only from the server-side). It's useful when we have some data on the server we want to give directly to the client-side.
For example, let's say we have sessions on the server-side and we can access the connected user through req.session.user. To give the authenticated user to our store, we update our store/index.js to the following:
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)
}
}
}

Vue.js: change parent variable from multiple layer down

This is my Vue Instance:
const app = new Vue({
el: '#app',
data() {
return {
loading: false
}
},
components: {
App,
Loading
},
router,store,
})
How can I change the loading variable from multiple layer down? It's not just simple parent/child change. It can be greatgrandparent/child change too.
There are several options, but the best way to answer the questions is to register as a global plug-in.
See the following solutions:
Create loading component and register it as a Vue component.
// loadingDialog.vue
<template>
<!-- ... -->
</template>
<script>
import { Loading } from 'path' // Insert the `loading` declared in number 2.
export default {
beforeMount () {
Loading.event.$on('show', () => {
this.show()
})
},
methods: {
show () {}
}
}
</script>
Creating a global plug-in
const loading = {
install (Vue, opt = {}) {
let constructor = Vue.extend(LOADING_COMPONENT)
let instance = void 0
if (this.installed) {
return
}
this.installed = true
Vue.prototype.$loadingDialog = {
show () {
if (instance) {
instance.show() // function of loading component
return
}
instance = new constructor({
el: document.createElement('div')
})
document.body.appendChild(instance.$el)
instance.show() // function of loading component
}
}
}
}
All components are accessible through the prototype.
this.$loadingDialog.show() // or hide()
Note, it is recommended that you control the api communication using the axios in the request or response interceptorof the axios at once.

AngularJS services in Vue.js

I'm new to Vue.js and looking for the equivalent of a service in AngularJS, specifically for storing data once and getting it throughout the app.
I'll be mainly storing the results of network requests and other promised data so I don't need to fetch again on very state.
I'm using Vue.JS 2.0 with Webpack.
Thanks!
I think what u are seeking for is vuex, which can share data from each component.
Here is a basic demo which from my code.
store/lottery.module.js
import lotteryType from './lottery.type'
const lotteryModule = {
state: {participantList: []},
getters: {},
mutations: {
[lotteryType.PARTICIPANT_CREATE] (state, payload) {
state.participantList = payload;
}
},
actions: {
[lotteryType.PARTICIPANT_CREATE] ({commit}, payload) {
commit(lotteryType.PARTICIPANT_CREATE, payload);
}
}
};
export default lotteryModule;
store/lottery.type.js
const PARTICIPANT_CREATE = 'PARTICIPANT_CREATE';
export default {PARTICIPANT_CREATE};
store/index.js
Vue.use(Vuex);
const store = new Vuex.Store();
store.registerModule('lottery', lotteryModule);
export default store;
component/lottery.vue
<template>
<div id="preparation-container">
Total Participants: {{participantList.length}}
</div>
</template>
<script>
import router from '../router';
import lotteryType from '../store/lottery.type';
export default {
data () {
return {
}
},
methods: {
},
computed: {
participantList() {
return this.$store.state.lottery.participantList;
}
},
created() {
this.$store.dispatch(lotteryType.PARTICIPANT_CREATE, [{name:'Jack'}, {name:'Hugh'}]);
},
mounted() {
},
destroyed() {
}
}
</script>
You don't need Vue-specific services in Vue2 as it is based on a modern version of JavaScript that uses Modules instead.
So if you want to reuse some services in different locations in your code, you could define and export it as follows:
export default {
someFunction() {
// ...
},
someOtherFunction() {
// ...
}
};
And then import from your Vue code:
import service from 'filenameofyourresources';
export default {
name: 'something',
component: [],
data: () => ({}),
created() {
service.someFunction();
},
};
Note that this is ES6 code that needs to be transpiled to ES5 before you can actually use it todays browsers.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export