I new on Nuxt JS and try tutorial on nuxt website, i make store in store/index.js
export const state = () => ({
mountain: [],
})
export const mutations = {
addMountain(state, mountain) {
state.mountain.push(mountain)
},
}
export const actions = {
async fetchMountain() {
const mountain = await fetch("https://api.nuxtjs.dev/mountains").then(
(res) => res.json()
);
// this.mountain = mountain
}
}
after that i make page on pages/index.js
<template>
<div>
<h1>Nuxt Mountains</h1>
<ul>
<li v-for="mount of mountain">{{ mount.title }}</li>
</ul>
<button #click="$fetch">Refresh</button>
</div>
</template>
<script>
import $store from "~/store";
export default {
fetch() {
return $store.state.mountain;
},
};
</script>
but i dont see anyting? someone can help me
This is how you can achieve this example.
/pages/index.vue
<template>
<div>
<h1>Nuxt Mountains</h1>
<p v-show="$fetchState.pending">Loading mountains...</p>
<ul v-show="!$fetchState.pending">
<li v-for="mountain of mountains" :key="mountain.slug">
{{ mountain.title }}
</li>
</ul>
<hr />
<button #click="$fetch">Refresh</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
async fetch() {
await this.fetchMountains()
},
computed: {
...mapState(['mountains']),
},
methods: {
...mapActions(['fetchMountains']),
},
}
</script>
Few notes on above:
mapState and mapActions is not mandatory here, you could access them via $store.dispatch etc directly but it's IMO more clean and quite explicit/good practice
don't forget the :key on the v-for, it's mandatory
prefer to use async/await everywhere rather than a mix with .then
/store/index.js
export const state = () => ({
mountains: [],
})
export const mutations = {
SET_MOUNTAINS(state, mountains) {
state.mountains = mountains
},
}
export const actions = {
async fetchMountains({ commit }) {
const response = await fetch('https://api.nuxtjs.dev/mountains')
const mountains = await response.json()
commit('SET_MOUNTAINS', mountains)
},
}
Few notes on above:
using UPPER_SNAKE_CASE is a nice convention for mutations
again, async + await combo
mountains (plural) seems more appropriate because we will have several ones
calling the mutation after the HTTP call in the action is the usual way to go with Nuxt2
the store object already exists within your context.
so you don't need to import it at all ...
example of how to use it:
computed: {
mountain () {
return this.$store.state.mountain;
}
}
ps. why inside the computed? because we don't need the component to be rerendered every time an update happens to the module. only if the data you are accessing got updated that should trigger rerendering ...
but that is also not the best practice.
you should always use modules.
to understand that better you should know that nuxt imports all the files inside the store folder automatically.
so every file you create inside the store file will be used as a new module in vuex store.
so for instance if you created a file called todo.js inside the store folder you will access it anywhere in the project using the following :
computed: {
mountain () {
return this.$store.state.todo.mountain;
}
}
i would suggest you take a look into that from here :
https://nuxtjs.org/docs/directory-structure/store/
Related
I'm in the process of migrating a Vue 2 app to Vue 3. It's gone well enough to this point, except that templates aren't updating based on Pinia state. I've looked at other SO questions related to this, but I can't resolve this problem.
Here are my components (parent and child) and store - Note, I'm using the options API
// Parent
<template>
<div class="wrapper">
<ActionButtonGroup />
</div>
</template>
<script>
import { useActionStore } from '#/stores/ActionStore';
import ActionButtonGroup from './components/ActionButtonGroup/main.vue';
export default {
name: 'ActionsParent'
components: {
ActionButtonGroup,
},
created() {
this.loadActions();
},
methods: {
loadActions() {
useActionStore().loadActions();
},
}
}
// Child
<template>
<div class="action-buttons d--f ai--c">
<Spinner class="ml-3" v-if="loading" />
</div>
</template>
<script>
import { mapState } from 'pinia';
import { useActionStore } from '#/stores/ActionStore';
import { Spinner } from '#/components/index';
export default {
name: 'ActionButtonGroup',
components: {
Spinner,
},
computed: {
...mapState(useActionStore, ['loading']),
}
}
</script>
// Store
import { ActionApi } from '#/api/index';
export const useActionStore = defineStore('Action', {
state: () => {
return {
loading: false,
};
},
actions: {
async loadActions() {
this.loading = true;
await this.fetchActions();
this.loading = false;
},
async fetchActions(id) {
const actions = await ActionApi.fetchActions();
return actions;
},
}
}
I've also tried using a setup function in the child, but that doesn't resolve the problem:
setup() {
const store = useActionStore();
const loading = computed(() => store.loading);
return {
loading
}
},
Sadly, I can't comment on this, so my response has to be an answer.
I don't think the problem has to do with Pinia. I checked and recreated it, and things are fine. I suggest looking into other areas, such as your Vue instance and the function itself that is being called. Try slowly removing things to bare bones to see if it'll work. You'll also want to confirm that after your "await" function "fetchActions" is called, it actually finishes, and the state updates.
If the state is updating, then you know it has to be related to the Vue instance. You could then try rebuilding in another Vue instance to help narrow down the issue.
I hope this helps!
I am try to use apollo-client with nextjs. Here I want to fetch data in getServerSideProps. Suppose I have 2 components and one page-
section.tsx this is component-1
const Section = () => {
return (
<div>
Section
</div>
);
};
export default Section;
mobile.tsx this is component 2
const Mobile = () => {
return (
<div>
Mobile
</div>
);
};
export default Mobile;
Now I call this two component into home page.
index.tsx-
const Home: NextPage = () => {
return (
<Container disableGutters maxWidth="xxl">
<Section />
<Mobile />
</Container>
);
};
export default Home;
export const getServerSideProps: GetServerSideProps = async () => {
const { data } = await client.query({ query: GET_USER_LIST })
return { props: {} }
}
Here you can see that in getServerSideProps I already fetch my data.
My question is How can I directly access this data form Section component and Mobile component without passing props. I don't want to pass props, because if my component tree will be more longer, then it will be difficult to manage props.
From appollo docs, I alreay know that apollo client do the same with redux state manager. So please tell me how can I access this data from any component that already fetched in getServerSideProps. Is it possible?. If not then how can what is the solutions.
How about using context api if you want to avoid prop drilling? By putting data into context, you can access it from any child component. Get the data from the SSR and put it into the context.
Below is the example
import React, {createContext, useContext} from "react";
export default function Home({data}) {
return <DataContext.Provider value={{data}}>
<div>
<Section/>
<Mobile/>
</div>
</DataContext.Provider>
}
export async function getServerSideProps() {
const data = 'hello world' //Get from api
return {
props: {data},
}
}
function Section() {
return <div>
Section
</div>
}
function Mobile() {
const context = useContext(DataContext);
return <div>
Mobile {context.data}
</div>
}
const DataContext = createContext({});
Now, as long as your tree structure grows within the DataContext provider, each child node will have access to data in the context.
Hope this helps.
In my store module /store/template.js I have:
const templateConfig = {
branding: {
button: {
secondary: {
background_color: '#603314',
background_image: ''
}
}
}
}
export const state = () => ({
branding: {},
...
})
export const actions = {
initializeStore (state) {
state.branding = templateConfig.branding
}
}
(initializeStore() is called when app initially loads)
I want to retrieve the branding the branding object in my component:
computed: {
...mapState({
branding: state => state.template.branding
})
}
But when trying to console.log() branding I see this:
Why don't I simply see the branding object? (and what on earth is this?)
You need to always use a mutation to change state. You can call one from your action:
export const mutations = {
SET_BRANDING(state, payload) {
state.branding = payload;
}
}
export const actions = {
initializeStore ({ commit }) {
commit('SET_BRANDING', templateConfig.branding);
}
}
What you're seeing with the observer is normal, and indicates that the branding object has been successfully mapped and accessed.
What you see is Vue's observable object, which is how Vue implements reactivity. Without this, there would be no reactivity, and you will see such a wrapper on all top-level reactive objects. You can pretend it's not there.
Vue in fact applies this same "wrapper" to the data object internally to make it observable:
Internally, Vue uses this on the object returned by the data function.
You won't see it on other reactive properties, but if they're reactive, they belong to some parent observable object.
You need to import { mapState, mapActions } from 'vuex' (already done I guess).
And then, you can write this
...mapState(['branding']) // or ...mapState('#namespacedModule', ['branding'])
Still, why do you not simply put the state directly (with your background_color) rather than going through a Vuex action ?
If you want to keep it this way, do not forget to await this.initializeStore() in your component before trying to access the state.
I have a store and a component. In my component and in created() I do an API fetch so my state changes and also in computed I use mapGetters so I could have access to my state. But when I do this nothing renders on the screen. I even use v-if directive to see if the value is undefined or not.
Also I should mention that in Vue devtools I can clearly see that the state updates correctly and I can see that the data is being fetch from API.
I already searched so much on the web and tried whatever I could find. Just one solution worked and it was to also have data inside my component but this solution is not a good solution due to duplication of the data (one local to component and one on the store).
So here is my code:
movies.js (store)
import axios from 'axios';
const state = {
heading: '',
movies: [],
};
const getters = {
getMovies: (state) => state.movies,
getHeading: (state) => state.heading,
};
const actions = {
async fetchTopRatedMovies({ commit }) {
const res = await axios.get(
`https://api.themoviedb.org/3/movie/top_rated?api_key=${process.env.VUE_APP_THEMOVIEDB_API_KEY}&language=en-US&page=1`
);
commit('setMovies', res.data.results);
commit('setHeading', 'Top Rated Movies');
},
};
const mutations = {
setMovies: (state, movies) => (state.movies = movies),
setHeading: (state, heading) => (state.heading = heading),
};
export default {
state,
getters,
actions,
mutations,
};
Movies.vue (component)
<template>
<div v-if="!heading || !movies">
<Spinner />
</div>
<div v-else>
<h5 class="center">{{ heading }}</h5>
<div v-for="movie in movies" :key="movie.id">
<p>{{ movie.title }}</p>
</div>
</div>
</template>
<script>
import Spinner from '../layout/Spinner';
import { mapActions, mapGetters } from 'vuex';
export default {
name: 'Movies',
components: {
Spinner,
},
methods: {
...mapActions(['fetchTopRatedMovies']),
},
computed: {
...mapGetters(['getMovies', 'getHeading']),
},
created() {
this.fetchTopRatedMovies();
},
};
</script>
<style></style>
One way to solve it is to change computed to:
computed: {
...mapGetters({
heading: 'getHeading',
movies: 'getMovies',
}),
},
but I wonder why this has to be like this. Because in the documentation it is written as I did in the code above.
computed: {
...mapGetters({
heading: 'getHeading',
movies: 'getMovies',
}),
},
The above code is working and the reason is, in your condition you are using v-if="!heading || !movies" as well as you are assigning values to variables headings and movies in the computed properties.
But when you use code like below mentioned, as the varaibles you are using in your condition, are not declared in your app so it is not working.
If you want to write the code like below, you can directly use these getter properties getMovies and getHeading in your app (like you use data properties). So in this case in your condition if you write like v-if="!getHeading|| !getMovies" then it will work.
computed: {
...mapGetters(['getMovies', 'getHeading']),
},
First I'm making the GET request and creating the Vue instance:
main.js
import Vue from 'vue';
import App from './App.vue';
let getData = () => { // currently unused - need to get promise return value into components
let url = '';
return fetch(url)
.then(response => {
return response.json();
})
.then(data => {
return data;
}
}
new Vue({
render: h => h(App)
}).$mount('#app');
Then I'm creating the top-level component that attaches to my index.html:
App.Vue
<template>
<div id="app">
<Badge></Badge>
<Card></Card>
</div>
</template>
<script>
import Badge from './components/Badge.vue';
import Card from './components/Card.vue';
export default {
name: 'app',
//props: ['data'], // unused, possible solution?
components: {
Badge,
Card
}
}
</script>
And then Badge.vue and Card.vue are both components that (need to) display different data from the fetch in main.js
I've tried using props to pass data from main.js -> App.vue -> Card.vue but I wasn't able to figure out how to do that with this code in main.js:
new Vue({
render: h => h(App)
}).$mount('#app');
I suspect render may be my problem; I'm using it from an example I followed in a tutorial - I'm pretty sure it's for the live webserver I'm using when I run vue-cli-service serve so maybe this is as simple as doing things differently to send props to App.vue
However, it seems like passing data through props this way is a bad idea and that I should be doing things differently, I just don't know what that would be, so I'm hoping there may be a more elegant solution. I'm only making a single ajax call which hopefully simplifies things, but it seems like using props this way can get too messy if I start adding more components.
You need to move getData to the App.vue
App.vue:
<template>
<div id="app">
<Badge></Badge>
<Card></Card>
</div>
</template>
<script>
import Badge from './components/Badge.vue';
import Card from './components/Card.vue';
export default {
name: 'app',
data: () => ({ // To store data in App.vue
someData: []
}),
components: {
Badge,
Card
},
created: {
this.getData(); // Call getData function when the component is created
},
methods: {
getData() {
// Making reference to component instance since we can't access `this` inside arrow function
const self = this;
let url = "";
fetch(url)
.then(response => {
return response.json();
})
.then(data => {
self.someData = data;
}
}
}
}
</script>
If you need to share data between multiple components, then i highly recommend you to use Vuex instead.