how to update component when props changes in nuxt - vue.js

I want to fetch data everytime when props changes in component and display it without reloading page.
pages/invoice/index.vue:
<template>
<div>
<b-table-column
field="InvoiceNo"
label="Invoice No"
sortable
v-slot="props"
>
<a #click="selectInvoice(props.row.id)">
{{ props.row.invoiceNumber }}
</a>
</b-table-column>
<Invoice :invoiceId="selectedInvoice" />
</div>
</template>
<script>
import axios from "axios";
import Invoice from "../../../components/Invoice.vue";
export default {
components: {
Invoice,
},
data() {
return {
selectedInvoice: "",
}
},
methods: {
selectInvoice(invoiceId) {
this.selectedInvoice = invoiceId;
},
}
}
</script>
components/Invoice.vue:
<script>
import axios from "axios";
export default {
props: ["invoiceId"],
data() {
return {
invoiceData: "",
};
},
watch: {
invoiceId: function (newVal, oldVal) {
this.fetchData(newVal)
},
deep: true,
immediate: true,
},
methods: {
async fetchData(invoiceId) {
let { data: invoiceDetails } = await axios.get(
`${process.env.backendapi}/invoice/byid?invoiceId=${invoiceId}`
);
return {
invoiceData: invoiceDetails,
};
},
},
};
</script>
When I select/change invoice, I can see the backend api getting called everytime with selected invoice, but invoiceData is always blank. The returned result is not getting updated in invoiceData.

I think you want the following in the fetchData method
this.invoiceData = invoiceDetails
Instead of
return {}
Only the already existing data and fetch vue/nuxt functions need to return an object

Related

How to get vuex pagination to be reactive?

I am not sure what the problem is. I am creating a Vue/Vuex pagination system that calls my api to get my list of projects. The page initially loads all the projects in when the page is mounted. The Vuex does the inital call with axios. Vuex finds the projects and the number of pages. Once the user clicks on the pagination that is created with the pagination component, it should automatically change the projects for page 2...3 etc.
The problem I have is that it is not reactive until you press the page number twice. I am using vue 3. I have tried not using Vuex and that was successful. I am trying to create a single store that does all the axios calls. Thanks for all your help!
Vuex store
import { createStore } from 'vuex';
import axios from 'axios';
/**
* Vuex Store
*/
export const store = createStore({
state() {
return {
projects: [],
pagination: {}
}
},
getters: {
projects: state => {
return state.projects;
}
},
actions: {
async getProjects({ commit }, page = 1) {
await axios.get("http://127.0.0.1:8000/api/guest/projects?page=" + page)
.then(response => {
commit('SET_PROJECTS', response.data.data.data);
commit('SET_PAGINATION', {
current_page: response.data.data.pagination.current_page,
first_page_url: response.data.data.pagination.first_page_url,
prev_page_url: response.data.data.pagination.prev_page_url,
next_page_url: response.data.data.pagination.next_page_url,
last_page_url: response.data.data.pagination.last_page_url,
last_page: response.data.data.pagination.last_page,
per_page: response.data.data.pagination.per_page,
total: response.data.data.pagination.total,
path: response.data.data.pagination.path
});
})
.catch((e) => {
console.log(e);
});
}
},
mutations: {
SET_PROJECTS(state, projects) {
state.projects = projects;
},
SET_PAGINATION(state, pagination) {
state.pagination = pagination;
}
},
});
Portfolio Component
<template>
<div>
<div id="portfolio">
<div class="container-fluid mt-5">
<ProjectNav></ProjectNav>
<div class="d-flex flex-wrap overflow-auto justify-content-center mt-5">
<div v-for="project in projects" :key="project.id" class="m-2">
<Project :project="project"></Project>
</div>
</div>
</div>
</div>
<PaginationComponent
:totalPages="totalPages"
#clicked="fetchData"
></PaginationComponent>
</div>
</template>
<script>
import Project from "../projects/Project.vue";
import PaginationComponent from "../pagination/PaginationComponent.vue";
import ProjectNav from "../projectNav/ProjectNav.vue";
/**
* PortfolioComponent is where all the projects are displayed.
*/
export default {
name: "PortfolioComponent",
data() {
return {
location: "portfolio",
projects: []
};
},
components: {
Project,
PaginationComponent,
ProjectNav,
},
mounted() {
this.fetchData(1);
},
computed: {
totalPages() {
return this.$store.state.pagination.last_page;
},
},
methods: {
fetchData(page) {
this.$store.dispatch("getProjects", page);
this.projects = this.$store.getters.projects;
},
},
};
</script>
In your fetchData method you are calling the async action getProjects, but you are not waiting until the returned promise is resolved.
Try to use async and await in your fetchData method.
methods: {
async fetchData(page) {
await this.$store.dispatch("getProjects", page);
this.projects = this.$store.getters.projects;
},
},

Vue received a Component which was made a reactive object

The problem I need to solve: I am writing a little vue-app based on VueJS3.
I got a lot of different sidebars and I need to prevent the case that more than one sidebar is open at the very same time.
To archive this I am following this article.
Now I got a problem:
Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref. (6)
This is my code:
SlideOvers.vue
<template>
<component :is="component" :component="component" v-if="open"/>
</template>
<script>
export default {
name: 'SlideOvers',
computed: {
component() {
return this.$store.state.slideovers.sidebarComponent
},
open () {
return this.$store.state.slideovers.sidebarOpen
},
},
}
</script>
UserSlideOver.vue
<template>
<div>test</div>
</template>
<script>
export default {
name: 'UserSlideOver',
components: {},
computed: {
open () {
return this.$store.state.slideovers.sidebarOpen
},
component () {
return this.$store.state.slideovers.sidebarComponent
}
},
}
</script>
slideovers.js (vuex-store)
import * as types from '../mutation-types'
const state = {
sidebarOpen: false,
sidebarComponent: null
}
const getters = {
sidebarOpen: state => state.sidebarOpen,
sidebarComponent: state => state.sidebarComponent
}
const actions = {
toggleSidebar ({commit, state}, component) {
commit (types.TOGGLE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
},
closeSidebar ({commit, state}, component) {
commit (types.CLOSE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
}
}
const mutations = {
[types.TOGGLE_SIDEBAR] (state) {
state.sidebarOpen = !state.sidebarOpen
},
[types.CLOSE_SIDEBAR] (state) {
state.sidebarOpen = false
},
[types.SET_SIDEBAR_COMPONENT] (state, component) {
state.sidebarComponent = component
}
}
export default {
state,
getters,
actions,
mutations
}
App.vue
<template>
<SlideOvers/>
<router-view ref="routerView"/>
</template>
<script>
import SlideOvers from "./SlideOvers";
export default {
name: 'app',
components: {SlideOvers},
};
</script>
And this is how I try to toggle one slideover:
<template>
<router-link
v-slot="{ href, navigate }"
to="/">
<a :href="href"
#click="$store.dispatch ('toggleSidebar', userslideover)">
Test
</a>
</router-link>
</template>
<script>
import {defineAsyncComponent} from "vue";
export default {
components: {
},
data() {
return {
userslideover: defineAsyncComponent(() =>
import('../../UserSlideOver')
),
};
},
};
</script>
Following the recommendation of the warning, use markRaw on the value of usersslideover to resolve the warning:
export default {
data() {
return {
userslideover: markRaw(defineAsyncComponent(() => import('../../UserSlideOver.vue') )),
}
}
}
demo
You can use Object.freeze to get rid of the warning.
If you only use shallowRef f.e., the component will only be mounted once and is not usable in a dynamic component.
<script setup>
import InputField from "src/core/components/InputField.vue";
const inputField = Object.freeze(InputField);
const reactiveComponent = ref(undefined);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = undefined;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
</script>
<template>
<component :is="reactiveComponent" />
</template>

Why action of Vuex returns a promise<pending>?

I have an action in Vuex actions which commit a mutation that it take a payload from the component, that is a number of the index for returning an object, it works fine on Vuex js file meaning that shows the selected item on the console, as I said it gets index from the payload,
but on the component, it gives me Promise <Pending>, why that's happening? for now, I do not use any API for my Nuxt/Vue app, but I will, and for now, I just want to know why this is happening and what is the best solution for solving this
Here my Vuex codes:
export const state = () => ({
articles: [
{
uid: 0,
img: 'https://raw.githubusercontent.com/muhammederdem/mini-player/master/img/1.jpg',
link: '/articles/1',
},
{
uid: 1,
img: 'https://raw.githubusercontent.com/muhammederdem/mini-player/master/img/2.jpg',
link: '/articles/2',
},
],
})
export const getters = {
getArticles(state) {
return state.articles
},
}
export const mutations = {
getSpeceficArticle(state, payload) {
return state.articles[payload]
},
}
export const actions = {
getSpeceficArticle({ commit }, payload) {
commit('getSpeceficArticle', payload)
},
}
and here my component codes:
<template>
<div class="article">
{{ getSpeceficArticle() }}
<div class="article__banner">
<img src="" alt="" />
</div>
<div class="article__text">
<p></p>
</div>
</div>
</template>
<script>
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
// computed: {},
methods: {
async getSpeceficArticle() {
return await this.$store.dispatch('articles/getSpeceficArticle', 0)
},
},
}
</script>
actions are used to update the state they are like mutations but the main difference between them is that actions can include some asynchronous tasks, if you want to get a specific article at given index you should use a getter named getArticleByIndex :
export const getters = {
getArticles(state) {
return state.articles
},
getArticleByIndex:: (state) => (index) => {
return state.articles[index]
}
}
then define a computed property called articleByIndex :
<script>
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
computed: {
articleByIndex(){
return this.$store.getters.articles.getArticleByIndex(0)
}
},
methods: {
},
}
</script>
#Mohammad if you find yourself using a lot of getters/actions etc from Vuex and they're starting to get a little wordy, you can bring in mapGetters from Vuex and rename your calls to something a little more convenient. So your script would become,
<script>
import { mapGetters } from 'vuex'
export default {
name: 'HomeArticlesArticle',
data() {
return {
item: '',
}
},
computed: {
articleByIndex(){
return this.getArticleByIndex(0)
}
},
methods: {
...mapGetters({
getArticleByIndex: 'articles/getArticleByIndex',
})
},
}
</script>
You can add ...mapGetters, ...mapActions to your computed section also.
since there is no web service call in vuex action, try to remove async and await keywords from the component.
Later when you add a webservice call than you can wrap action body in new Promise with resolve and reject and then you can use async and await in component. let me know if this works for you.

Get object from Vuex at specific index

I am getting an array from Vuex and I want an object at 2 positions.
HTML
<p class="">
{{ MainImg[2].para}}
</p>
Vue
export default {
name: "App",
components: { },
data() {
return {
imageQuery: this.$route.params.image,
};
},
computed: {
...mapGetters("design", {
MainImg: ["singleDesigns"]
})
},
created() {
this.fetchDesigns();
},
mounted() {
console.log(this.MainImg);
},
methods: {
fetchDesigns() {
this.$store.dispatch("design/getSingleDesign", this.imageQuery);
}
}
};
But it shows an undefined error.
And When I add MainImg array in Vue data like this.
data() {
return {
imageQuery: this.$route.params.image,
MainImg:[{para:"1"},{para:"2"},{para:"3"},{para:"4"}]
};
It Works.
P.S.-
Store Code-
export const state = () => ({
designs: [],
})
export const getters = {
singleDesigns(state) {
return state.designs;
}
}
I am not adding Action and Mutation because it works fine with other code.
It looks like the array is empty at the first rendering, so you should add a condition to render it :
<p class="" v-if="MainImg && MainImg.length >= 2">
{{ MainImg[2].para}}
</p>

How do I trigger an AJAX request when props is changed?

I have a this App component
<template>
<div id="app">
<Component1 #addItem="addItem" />
<Component2 :items="items" />
</div>
</template>
<script>
import Component1 from './components/Component1'
import Component2 from './components/Component2'
export default {
name: 'app',
components: { Component1, Component2 },
data: function () {
return {
items: [],
}
},
methods: function () {
addItem(item) {
items.push(item)
},
},
}
</script>
This is my Component2 component:
<template>
<div>
{{ ajax_data }}
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Component2',
props: ['items'],
data: function () {
return {
ajax_data: null
}
},
mounted () {
this.callAJAX()
},
methods: {
callAJAX() {
axios
.get('/api/get-some-data', {
params: {
items: items
}
})
.then((response) => {
this.ajax_data = response.data
})
},
},
}
</script>
I want to trigger the AJAX everytime I add an item. The problem with my code is since Component2 is already mounted and when an item is added the AJAX is not running. So then I added this hook:
updated () {
this.callAJAX()
},
The problem with this is its running an infinite loop.
Is there a proper way to do this?
You can simply detect if value change with a watcher
https://v2.vuejs.org/v2/guide/computed.html#Watchers
https://v2.vuejs.org/v2/api/#watch
in your case, you may set the deep property to true...