I'm trying to build pagination using vuetify pagination component and nuxt.js but it's not work with server side rendering.
I added nuxt.js link using code from https://github.com/vuetifyjs/vuetify/issues/4855. It's work on client side but on server side it return error "render function or template not defined in component: anonymous"
Anyone have idea how to build correct SSR pagination or how to fix my solution?
Don't know if this thread is still valid but this is my solution for vuetify pagination with nuxt.
<script>
import PostList from "#/components/site/PostList";
import axios from "axios";
export default {
components: {
PostList
},
watchQuery: ["page"],
async asyncData({ $axios, query }) {
try {
const page = query.page || 1;
let { data } = await $axios.get("/articles/?page=" + page);
return {
posts: data.data,
page: data.current_page,
totalPages: data.last_page
};
} catch (e) {
// console.log(e); display errors
}
},
methods: {
next() {
this.$router.push({ query: { page: this.page } });
}
}
};
</script>
<template>
<v-pagination v-model="page" :length="totalPages" #input="next"></v-pagination>
</template>
Related
I am trying to migrate a small project from Nuxt2 to Nuxt3. in Nuxt2, I used axios for making API calls.
now i want to fetch in nuxt3 but Axios doesn't work here.
how to migrate this code to usefetch method in Nuxt3.
this is what i had in nuxt2
<script>
import axios from "axios";
export default {
data() {
return {
allDestinations: [],
allSubDestinations: [],
allTours: [],
form: "",
};
},
async asyncData({ route }) {
let { data: countrydata } = await axios.get(
`${process.env.backendapi}/dests/getcountry/${route.query.countryid}`
);
let { data: allDest } = await axios.get(
`${process.env.backendapi}/dests/getmaindests?limit=5000`
);
let { data: allSubDest } = await axios.get(
`${process.env.backendapi}/dests/getsubdests?limit=5000`
);
let { data: alltours } = await axios.get(
`${process.env.backendapi}/tours/gettours?limit=10000`
);
return {
form: countrydata,
allDestinations: allDest.results,
allSubDestinations: allSubDest.results,
allTours: alltours.results,
};
},
};
</script>
The equivalent to that in Nuxt3would be the following.
.env
NUXT_PUBLIC_TEST_URL="https://jsonplaceholder.typicode.com"
nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
runtimeConfig: {
public: {
testUrl: '', // fallback empty string, must be present tho
},
},
})
With this in any page
<template>
<section>
<div>{{ todo.title }}</div>
<div>{{ user.email }}</div>
<div>{{ photos }}</div>
</section>
</template>
<script setup>
const { testUrl } = useRuntimeConfig()
const route = useRoute() // let's suppose that `countryid` equals 1
const { data: todo } = await useFetch(`${testUrl}/todos/1`)
const { data: user } = await useFetch(`${testUrl}/users/${route.query.countryid}`)
const { data: photos } = await useFetch(`${testUrl}/photos/`)
</script>
More details regarding data fetching can be found here: https://v3.nuxtjs.org/guide/features/data-fetching
But overall, useFetch is blocking and doing what you expect.
If you cannot use script setup, you need to write it like this: https://v3.nuxtjs.org/guide/features/data-fetching#using-async-setup
Here is the documentation regarding env variables: https://v3.nuxtjs.org/guide/features/runtime-config#environment-variables
Lets say we injected this repository on a plugin/service-container.js
import nodeFetch from 'node-fetch'
import { AbortController as NodeAbortController } from 'node-abort-controller'
import HttpClient from '#/services/httpClient'
import PostRepository from '#/repositories/posts'
export default ({ app }, inject) => {
if (!process.client || app.context.env.NUXTJS_DEPLOY_TARGET === 'server') {
inject('postRepository', postRepository)
}
}
I have always acceded to API repositories from the asyncData method, like so:
export default {
async asyncData ({ $postRepository, }) {
const posts = await $postRepository.getAllPaginated(page, 11)
return {
posts,
}
}
}
But I need to access to it in a method, this is actually working but:
I doesn't look the right way because i'm caching in the component's data()
It fires this lint error:
Async method 'asyncData' has no 'await' expression.eslintrequire-await
What's the right way? I Can't find it online (the only examples I found involved using the Store)
export default {
async asyncData ({ $postRepository }) {
this.$postRepository = $postRepository
},
methods: {
async loadMore () {
if (this.page < this.posts.numPages) {
const posts = await this.$postRepository.getAllPaginated(this.page + 1, 11)
}
}
}
}
The error is coming from here
async asyncData ({ $postRepository }) {
this.$postRepository = [missing await here] $postRepository
},
From the documentation
This hook can only be used for page-level components. Unlike fetch, asyncData cannot access the component instance (this). Instead, it receives the context as its argument. You can use it to fetch some data and Nuxt will automatically shallow merge the returned object with the component data.
Hence, you cannot use any kind of this.loadMore in asyncData because it doesn't have access to the instance yet. So, inject is indeed the proper way of doing things.
With a plugin like that
export default ({ _ }, inject) => {
inject('customTest', async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
return await response.json()
})
}
And a page like this
<template>
<div>
<pre>item: {{ item }}</pre>
</div>
</template>
<script>
export default {
async asyncData({ $customTest }) {
const item = await $customTest()
return { item }
},
}
</script>
It is not calling a method but you could totally use this.$nuxt.refresh() to fetch it again and increment the index of the repository call after an update in the store.
Which could be referenced like
await fetch(`https://jsonplaceholder.typicode.com/todos/${indexFromVuex}`)
You could of course keep it local too
<template>
<div>
<pre>item: {{ item }}</pre>
<button #click="fetchNewItem">fetch new item</button>
</div>
</template>
<script>
export default {
async asyncData({ $customTest }) {
const item = await $customTest()
return { item }
},
data() {
return {
index: 1,
}
},
methods: {
async fetchNewItem() {
this.index += 1
this.item = await this.$customTest(this.index)
},
},
}
</script>
So yeah, I don't think that there are other possible approaches with asyncData.
The fetch() hook is a bit more flexible but it's also totally different too regarding how it is working.
Anyway, with those 2 approaches you could totally have enough to solve the issue of your HTTP call.
It seems that an injected dependency can be accessed (in this case) with simply this.$postRepository inside any method so I didn't even need that asyncData
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)
}
}
}
I have an API endpoint for https on localhost that's serving raw json data like so:
[{"day":"Monday","hours":43,"date":"2019-12-23T14:38:08.2112312-05:00"},{"day":"Tuesday","hours":36,"date":"2019-12-24T14:38:08.2130238-05:00"},{"day":"Wednesday","hours":39,"date":"2019-12-25T14:38:08.2130318-05:00"},{"day":"Thursday","hours":34,"date":"2019-12-26T14:38:08.2130329-05:00"},{"day":"Friday","hours":42,"date":"2019-12-27T14:38:08.2130338-05:00"}]
What I don't understand is why VUE.js is failing with a "TypeError: "NetworkError when attempting to fetch resource." JSON is being surved for localhost use, and cors has been enabled. Any get request from postman, browser, curl, etc works just fine. It's only failing when VUE.js makes the fetch() call.
The VUE component:
<template>
<div class="container">
<LineChart v-if="loaded" :chartdata="chartdata" :options="options" />
</div>
</template>
<script>
import LineChart from './LineChart.vue';
export default {
name: 'LineChartContainer',
components: { LineChart },
data: () => ({
loaded: false,
chartdata: null,
}),
async mounted() {
this.loaded = false;
try {
const { daysWorked } = await fetch(
'https://localhost:5001/api/timeworked',
);
console.log('days worked data:', daysWorked);
this.chartdata = daysWorked;
this.loaded = true;
} catch (e) {
console.error("Couldn't access data:", e);
}
},
};
</script>
Any thoughts, advice, or tips appreciated!
It's not a problem with Vue. It's a problem with the way you fetch.
Take a look at https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API and https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
try
...
const { daysWorked } = await fetch('https://localhost:5001/api/timeworked')
.then(r=>r.json());
...
this should help.
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.