Conditional get request in vue for rendering a subcomponent scoped - javascript

When I click a profile (of an author) component, I can't figure out how it should render a scoped sub-component, listing the main entities of the app, so-called fabmoments (containers for 3D print information).
My current solution looks like this:
export default {
name: 'Multipe',
props: [
'author'
],
data () {
return {
// search: '',
localAuthor: '',
fabmoments: []
}
},
created () {
this.localAuthor = this.author
if (typeof localAuthor !== 'undefined') {
this.$http.get(`/users/${this.$route.params.id}/fabmoments`)
.then(request => this.buildFabmomentList(request.data))
.catch(() => { alert('Couldn\'t fetch faboments!') })
} else {
this.$http.get('/fabmoments')
.then(request => this.buildFabmomentList(request.data))
.catch(() => { alert('Couldn\'t fetch faboments!') })
}
},
methods: {
buildFabmomentList (data) {
this.fabmoments = data
}
},
components: {
// Box
}
}
This renders all in the profile, where it should render a list scoped to the current profile's author.
And it renders nothing in the home (without receiving the prop), where it should render all.
I am not much of star in JavaScript. What am I doing wrong?
UPDATE
This works as a solution, though not very elegant.
export default {
name: 'Multipe',
props: [
'author'
],
data () {
return {
fabmoments: []
}
},
created () {
if (this.author.id >= 0) {
this.$http.get(`/users/${this.$route.params.id}/fabmoments`)
.then(request => this.buildFabmomentList(request.data))
.catch(() => { alert('Couldn\'t fetch faboments!') })
} else {
this.$http.get('/fabmoments')
.then(request => this.buildFabmomentList(request.data))
.catch(() => { alert('Couldn\'t fetch faboments!') })
}
},
methods: {
buildFabmomentList (data) {
this.fabmoments = data
}
},
components: {
// Box
}
}

Not sure which part is wrong, but you may definitely debug your code to find out why fabmoments is empty array assuming there is no error occurred yet.
There are three parts to debug:
http response -- to check if data is properly returned
this -- to check if this pointer still points at the component
template -- to check if fabmoments are correctly bind to the element
At last, it would be better to separate your http request logics from your components.
Good luck!

Related

Pinia|Vue3 I can't access the property of the object that returned from the Pinia action

first of all I am using the Mockjs to simulate the backend data:
{
url: "/mockApi/system",
method: "get",
timeout: 500,
statusCode: 200,
response: { //
status: 200,
message: 'ok',
data: {
'onlineStatus|3': [{
'statusId': '#integer(1,3)',
'onlineStatusText': '#ctitle(3)',
'onlineStatusIcon': Random.image('20*20'),
'createTime': '#datetime'
}],
'websiteInfo': [{
'id|+1': 1,
}]
}
}
}
the data structure would be: https://imgur.com/a/7FqvVTK
and I retrieve this mock data in Pinia store:
import axios from "axios"
import { defineStore } from "pinia"
export const useSystem = defineStore('System', {
state: () => {
return {
systemConfig: {
onlineStatus: [],
},
}
},
actions: {
getSystemConfig() {
const axiosInstance = axios.interceptors.request.use(function (config) {
// Do something before request is sent
config.baseURL = '/mockApi'
return config
}, function (error) {
// Do something with request error
return Promise.reject(error);
})
axios.get('/system/').then(res => {
this.systemConfig.onlineStatus = res.data.data.onlineStatus
})
// console.log(res.data.data.onlineStatus)
axios.interceptors.request.eject(axiosInstance)
}
}
})
I use this store in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]
},
},
components: {UserOnlineStatus }
}
template in Profile.vue I import the child component userOnlineStatus.vue
<UserOnlineStatus :userCurrentOnlineStatus="userData.onlineStatus">
{{ showUserOnlineStatusText }}
</UserOnlineStatus>
here is what I have got https://imgur.com/fq33uL8
but I only want to get the onlineStatusText property of the returned object, so I change the computed code in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]['onlineStatusText']//👀I chage it here!
},
},
components: {UserOnlineStatus }
}
but I will get the error in the console and it doesn't work:
https://imgur.com/Gb68Slk
what should I do if I just want to display the specific propery of the retrived data?
I am out of my wits...
I have tried move the store function to the child components, but get the same result.
and I google this issue for two days, nothing found.
Maybe it's because of I was trying to read the value that the Profile.vue hasn't retrieved yet?
in this case, how could I make sure that I have got all the value ready before the page rendered in vue3? Or can I watch this specific property changed, then go on rendering the page?
every UX that has data is coming from remote source (async data) should has spinner or skeleton.
you can use the optional chaining for safe access (if no time to await):
return this.getUserOnlineStatus?.[this.getUserOnlineStatusIndex - 1]?.['onlineStatusText']

Force page to refresh state when you return to it

I am using Nuxt in SPA mode and have a page structure like this:
pages
...
- users/
- - index
- - new
- - update/
- - - _id
...
I have a page of users with a list of them and 'subpage' - new.
On my users/index page I am fetching my users in asyncData Hook like this:
async asyncData({ app: { apolloProvider }, store: { commit, dispatch } }) {
const {
data: { getAllUsers: { success, users, message } },
} = await apolloProvider.defaultClient.query({
query: getAllUsersGQL,
})
if(success) {
await commit('user/setUsers', users, { root: true })
} else {
dispatch('snackbar/notify', { message: message, type: 'error' }, { root: true })
}
},
It seems to work as it should. But when I go to my page users/new, fill up the form and send it, update the store and redirect to my users/index page, I encounter kinda interesting behaviour.
The problem here is that I don't have a newly updated state but some kinda cached one or previous state. I can so far make it working with location.replace. When the page reloads I have an accurate and updated state.
That's how I'm handling redirect on users/new page:
async save() {
if(this.$refs.create.validate()) {
this.loading = true
delete this.form.confirm
await this.createUserStore(this.form)
this.$router.push(
this.localeLocation({
name: 'users',
})
)
this.loading = false
this.$refs.create.reset()
}
},
and that's how I am refreshing my state in Vuex:
export const mutations = {
updateUsers: (state, payload) => {
state.users = [...state.users, payload].sort((a,b) => a.createdAt - b.createdAt)
},
}
That's how I'm passing data:
computed: {
...mapGetters({
storeUsers: 'user/getUsers',
storeGetMe: 'auth/getMe',
}),
},
<v-data-table
:headers="headers"
:items="storeUsers"
:search="search"
item-key="id"
class="elevation-1"
dense
>
</v-data-table>
I already tried to list items using v-for and it doesn't work either.
And when I console.log state I get all items. It works as it should.
What can be the problem that it's not updating the view?
If anyone has ever faced such kind of behaviour I'd appreciate any hints.
This is probably coming from the fact that Apollo does have it's own cache and that it reaches for the cache first as cache-first is the default value.
Give this one a try
await apolloProvider.defaultClient.query({
query: getAllUsersGQL,
fetchPolicy: 'network-only',
})
Additional answer
This is an example of a dynamic GQL query that I previously wrote
test.gql.js
import { gql } from 'graphql-tag'
import { constantCase, pascalCase } from 'change-case'
export const queryCompanyBenefitInfo = ({
benefitType,
needCifEligibility = false,
needActiveOnWeekend = false,
needCompanyContribution = false,
needAutoRenewed = false,
needUnitValue = false,
}) => {
return gql`
query {
CompanyBenefit {
oneOfType(benefitType: ${constantCase(benefitType)}) {
... on Company${pascalCase(benefitType)}Benefit {
${needCifEligibility ? 'cifEligibility' : ''}
${needActiveOnWeekend ? 'activeOnWeekend' : ''}
${needCompanyContribution ? 'companyContribution' : ''}
${needAutoRenewed ? 'autoRenewed' : ''}
${
needUnitValue
? `unitValue {
value
}`
: ''
}
}
}
}
}
`
}
And call it this way
import { testQuery } from '~/apollo/queries/test.gql.js'
...
await this.app.apolloProvider.defaultClient.query({
query: testQuery({ benefitType: 'care', needCifEligibility: true }),
fetchPolicy: 'network-only',
errorPolicy: 'all',
})

How to prevent Vue from loading the data multiple times?

I'm currently practicing Vue with Vuex and the REST API architecture (with Django). I wanna retrieve the data from by database and store the data in my global state in order to access it throughout my Vue components. It works fine so far but there is one weird thing I do not understand.
When I start my application I have the homepage where currently nothing is displayed. I click on the menu item "Contacts" and the contacts component loads (router-view) (API GET call is executed) and displays the table of all the created contacts and shows it properly (source of truth is now my global state). I can edit, delete and view a contact.The contacts are now stored in the global state as well.
The problem: Everytime I load the contact component the mounted() lifecycle gets called (which makes sense) and loads the contacts from the API inside the state so the whole lists gets dupblicated over and over. I just want Vue to make a GET request only once and then access the state's data (where to contacts are stored now).
Another scenario is when I update a contact and click back to the contacts menu item the list contains the old contact and the updated one but when I refresh the page it is fine.
Thanks!
MY CODE
state.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
import { apiService } from "../utils/api.service.js";
export const store = new Vuex.Store({
state: {
contacts: []
},
mutations: {
initContacts_MU: (state, data) => {
state.contacts.push(...data);
},
updateContact_MU: (state, data) => {
let getContact = state.contacts.filter(contact => contact.id === data.id);
let getContactIndex = state.contacts.indexOf(getContact);
state.contacts.splice(getContactIndex, 1, data);
},
deleteContact_MU: (state, data) => {
let getContact = state.contacts.filter(contact => contact.id === data.id);
let getContactIndex = state.contacts.indexOf(getContact);
state.contacts.splice(getContactIndex, 1);
},
createContact_MU: (state, data) => {
state.contacts.unshift(data);
}
},
actions: {
initContacts_AC({ commit }) {
let endpoint = "api/contacts/";
apiService(endpoint).then(data => {
commit("initContacts_MU", data);
});
},
updateContact_AC({ commit }, contact) {
let endpoint = "/api/contacts/" + contact.slug + "/";
apiService(endpoint, "PUT", contact).then(contact => {
commit("updateContact_MU", contact);
});
},
deleteContact_AC({ commit }, contact) {
let endpoint = "/api/contacts/" + contact.slug + "/";
apiService(endpoint, "DELETE", contact).then(contact => {
commit("deleteContact_MU", contact);
});
},
createContact_AC({ commit }, contact) {
let endpoint = "/api/contacts/";
apiService(endpoint, "POST", contact).then(contact => {
commit("createContact_MU", contact);
});
}
},
getters: {
contacts: state => {
return state.contacts;
}
}
});
ContactList.vue
<script>
import Contact from "../../components/Contacts/Contact.vue";
import { mapGetters, mapActions } from "vuex";
export default {
name: "ContactList",
components: {
Contact
},
computed: {
...mapGetters(["contacts"])
},
methods: {
...mapActions(["initContacts_AC"])
},
mounted() {
this.initContacts_AC();
}
};
</script>
Just check if there're contacts which are already retrieved from backend.
computed: {
...mapGetters(["contacts"])
},
methods: {
...mapActions(["initContacts_AC"])
},
mounted() {
if (this.contacts && this.contacts.length > 0) return; // already fetched.
this.initContacts_AC();
}
EDIT:
updateContact_MU: (state, data) => {
const contactIndex = state.contacts.findIndex(contact => contact.id === data.id);
if (contactIndex < 0) return;
state.contacts.splice(contactIndex, 1, data);
}

Vue js Laravel - Axios making Multiple ( 2 ) Requests when changing pages , Category and Subcategory

I'm building an application with Vue on the frontend and Laravel PHP on the backend. Its a single page app (SPA).
When changing pages, sometimes - not always - axios makes two requests for the same page. I'm having trouble figure it out what is happening.
When the link changes I have two watchers, one for the top category and another for the sub-category. They trigger the created () hook that calls the loadData method.
If I change the main category and sub category ( Example: from 1/5 to 2/31 ), the loadData method is called two time. How can I fix this ?
Google Network Tab (The .json request does not represent the same type of page that I'm referring above, only the numbers ) :
<script>
import axios from 'axios'
import { mapGetters } from 'vuex'
import Form from 'vform'
export default {
data() {
return {
products: {
cat : {} ,
products : []
},
productsShow: '',
quickSearchQuery: '',
loadeddata : false,
}
},
methods: {
loadMore () {
this.productsShow += 21
},
loadData () {
if ( this.$route.params.sub_id ) {
axios.get('/api/cat/' + this.$route.params.cat_id + '/' + this.$route.params.sub_id).then(response => {
this.products = response.data
this.productsShow = 21
this.loadeddata = true
}).catch(error => {
if (error.response.status === 404) {
this.$router.push('/404');
}
});
} else {
axios.get('/api/cat/' + this.$route.params.cat_id ).then(response => {
this.products = response.data
this.productsShow = 21
this.loadeddata = true
}).catch(error => {
if (error.response.status === 404) {
this.$router.push('/404');
}
});
}
},
computed: {
...mapGetters({locale: 'lang/locale', locales: 'lang/locales' , user: 'auth/user'}),
filteredRecords () {
let data = this.products.products
data = data.filter( ( row ) => {
return Object.keys( row ).some( ( key ) => {
return String( row[key] ).toLowerCase().indexOf(this.quickSearchQuery.toLowerCase()) > -1
})
})
return data
}
},
created() {
this.loadData()
},
watch: {
'$route.params.cat_id': function (cat_id) {
this.quickSearchQuery = ''
this.loadData()
},
'$route.params.sub_id': function (sub_id) {
this.quickSearchQuery = ''
this.loadData()
}
}
}
</script>
So I see that you figured the issue by yourself.
To fix that, you can delay the loadData function (kind of debounce on the tail) so if it's being called multiple times in less than X (300ms?) then only the last execution will run.
try:
loadData () {
clearTimeout(this.loadDataTimeout);
this.loadDataTimeout = setTimeout(()=> {
//The rest of the loadData function should be placed here
},300);
}
Resolved :
watch: {
'$route.params': function (cat_id, sub_id) {
this.quickSearchQuery = ''
this.loadData()
},

Vue.js: mutation for deleting a comment

I have been working on the feature of comment deleting and came across a question regarding a mutation for an action.
Here is my client:
delete_post_comment({post_id, comment_id} = {}) {
// DELETE /api/posts/:post_id/comments/:id
return this._delete_request({
path: document.apiBasicUrl + '/posts/' + post_id + '/comments/' + comment_id,
});
}
Here is my store:
import Client from '../client/client';
import ClientAlert from '../client/client_alert';
import S_Helper from '../helpers/store_helper';
const state = {
comment: {
id: 0,
body: '',
deleted: false,
},
comments: [],
};
const actions = {
deletePostComment({ params }) {
// DELETE /api/posts/:post_id/comments/:id
document.client
.delete_post_comment({ params })
.then(ca => {
S_Helper.cmt_data(ca, 'delete_comment', this);
})
.catch(error => {
ClientAlert.std_fail_with_err(error);
});
},
};
delete_comment(context, id) {
context.comment = comment.map(comment => {
if (!!comment.id && comment.id === id) {
comment.deleted = true;
comment.body = '';
}
});
},
};
export default {
state,
actions,
mutations,
getters,
};
I am not quite sure if I wrote my mutation correctly. So far, when I am calling the action via on-click inside the component, nothing is happening.
Guessing you are using vuex the flow should be:
according to this flow, on the component template
#click="buttonAction(someParams)"
vm instance, methods object:
buttonAction(someParams) {
this.$store.dispatch('triggerActionMethod', { 'something_else': someParams })
}
vuex actions - Use actions for the logic, ajax call ecc.
triggerActionMethod: ({commit}, params) => {
commit('SOME_TRANSATION_NAME', params)
}
vuex mutations - Use mutation to make the changes into your state
'SOME_TRANSATION_NAME' (state, data) { state.SOME_ARG = data }

Categories

Resources