How to fetch slug object in /layouts/default.vue? - vue.js

In my /projects/_slug.vue I have the line:
<Header :title="project.title" :subtitle="project.subtitle" />
by fetching the object in the same file with:
async asyncData({ $content, params }) {
const project = await $content("projects", params.slug).fetch();
return { project };
}
Now my question: I'd like to move Header out of /projects/_slug.vue to /layouts/default.vue. Is it somehow possible to get project.title and project.subtitle in this file?

Layouts don't have asyncData, but they support the fetch hook. There, you could access the Nuxt context via $nuxt.context, which contains $content() and $route (for params):
<template>
<Header :title="project.title" :subtitle="project.subtitle" />
</template>
<script>
export default {
data() {
return {
project: {}
}
},
async fetch() {
const { $content, route } = this.$nuxt.context;
const { params } = route;
this.project = await $content("hello", params.slug).fetch();
},
}
</script>

Related

How to access a injected repository from a component's method

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 + Jest + Composition API: How to check if an action has been called

I am working on a project built on Vue3 and composition API and writing test cases.
The component I want to test is like below.
Home.vue
<template>
<div>
<Child #onChangeValue="onChangeValue" />
</div>
</template>
<script lang="ts>
...
const onChangeValue = (value: string) => {
store.dispatch("changeValueAction", {
value: value,
});
};
</scirpt>
Now I want to test if changeValueAction has been called.
Home.spec.ts
...
import { key, store } from '#/store';
describe("Test Home component", () => {
const wrapper = mount(Home, {
global: {
plugins: [[store, key]],
},
});
it("Test onChangeValue", () => {
const child = wrapper.findComponent(Child);
child.vm.$emit("onChangeValue", "Hello, world");
// I want to check changeValueAction has been called.
expect(wrapper.vm.store.state.moduleA.value).toBe("Hello, world");
});
});
I can confirm the state has actually been updated successfully in the test case above but I am wondering how I can mock action and check if it has been called.
How can I do it?
I have sort of a similar setup.
I don't want to test the actual store just that the method within the component is calling dispatch with a certain value.
This is what I've done.
favorite.spec.ts
import {key} from '#/store';
let storeMock: any;
beforeEach(async () => {
storeMock = createStore({});
});
test(`Should remove favorite`, async () => {
const wrapper = mount(Component, {
propsData: {
item: mockItemObj
},
global: {
plugins: [[storeMock, key]],
}
});
const spyDispatch = jest.spyOn(storeMock, 'dispatch').mockImplementation();
await wrapper.find('.remove-favorite-item').trigger('click');
expect(spyDispatch).toHaveBeenCalledTimes(1);
expect(spyDispatch).toHaveBeenCalledWith("favoritesState/deleteFavorite", favoriteId);
});
This is the Component method:
setup(props) {
const store = useStore();
function removeFavorite() {
store.dispatch("favoritesState/deleteFavorite", favoriteId);
}
return {
removeFavorite
}
}
Hope this will help you further :)

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)
}
}
}

Nuxt head() does not wait asyncData response for head

I have a nuxt code like this
<template>
<section>
<div>Hello Nuxt</div>
</section>
</template>
<script>
const fetchTheme = () => {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve({
title: "Fetched Title"
});
}, 100);
});
};
export default {
async asyncData() {
const theme = await fetchTheme();
return theme;
},
head() {
if (this.theme) {
return {
title: this.theme.title
};
} else {
return {
title: "Default title"
};
}
}
};
</script>
<style scoped>
</style>
While I do view source, it gives 'Default title' but I need the title fetched from API
Here is Code Code Sandbox
From the docs on asyncData:
Nuxt.js will automatically merge the returned object with the component data.
That means that what you're doing:
async asyncData() {
const theme = await fetchTheme();
return theme;
}
is analogous to this:
async asyncData() {
const theme = await fetchTheme();
return {
title: theme.title
};
}
Which means that the title is accessible by doing this.title instead of this.theme.title.
To fix this, simply modify the return format of asyncData, to return an object that has a theme property:
async asyncData() {
const theme = await fetchTheme();
return {
theme
};
}
This will properly add the theme property to the data property of the component.

Vue-Router: Cannot read property '$route' of undefined - VueJS

Please review my code.
<template>
<div class="row flex">
<div class="col-md-6 home_feed">
<post-detail :posts="posts"></post-detail>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
async asyncData (params) {
let { data } = await axios.get('http://localhost:8000/api/v1/users/' + this.$route.params.id + '/')
return {
posts: data
}
},
components: {
'post-detail': Item
}
}
</script>
I got this error: Cannot read property '$route' of undefined when I asyncdata from params.id, but when I type: console.log(this.$route.params.id), it goes right. How can I fix this
if you want to load data from server (from browser) in mounted lifecycle try this:
export default {
data() {
return {
data: {}
}
},
mounted() {
this.asyncData();
},
methods: {
async asyncData ({ route }) {
let { data} = await axios.get('http://localhost:8000/api/v1/users/' + this.$route.params.id + '/')
this.data = data
}
}
}
Response from server will be available in response variable.
For SSR you can do :
async asyncData ({ store, route }) {
let { data} = await axios.get('localhost:8000/api/v1/users/'; + route.params.id + '/')
return {
posts: data
}
}
asyncData will be called before the components are instantiated, and it doesn't have access to this. (see https://ssr.vuejs.org/en/data.html Logic Collocation with Components for details)
For SSR you can change
<script>
async asyncData ({ store, route }) {
let { data} = await axios.get('localhost:8000/api/v1/users/' + this.$route.params.id + '/')
return {
posts: data
}
}
</script>
to
<script>
async asyncData ({ route }) {
let { data} = await axios.get('localhost:8000/api/v1/users/' + route.params.id + '/')
return {
posts: data
}
}
</script>
According to the nuxt tutorial you can not have access to this inside asyncData because it is called before initiating the component. Nuxt Documentation
#Mikhail
This code is success:
export default {
data() {
return {
data: {}
}
},
mounted() {
this.asyncData();
},
methods: {
async asyncData ({ route }) {
let { data} = await axios.get('http://localhost:8000/api/v1/users/' + route.params.id + '/')
this.data = data
}
}
}
But when get API parent-children data like this: {{data.user.username}}, data.user goes undefined. So API data goes error.
I use Nuxt and your code for SSR not work:
Error: Cannot read property $route of undefined
<script>
async asyncData ({ store, route }) {
let { data} = await axios.get('localhost:8000/api/v1/users/'; + this.$route.params.id + '/')
return {
posts: data
}
}
</script>