I am learning Vue JS and I am getting data from the backend using Axios to show it in data table. When I get the data and do a v-for I get No data available in table Below is my code.
Picture
Template
<tbody >
<tr v-for="user in users" v-bind:key="user">
<td>{{user.firstname}} {{user.lastname}}</td>
<td>{{user.username}}</td>
<td>{{user.role}}</td>
<td>{{user.email}}</td>
<td>{{user.is_active}}</td>
<td>View</td>
</tr>
</tbody>
And script
import axios from 'axios';
export default {
name : "SystemAdmin",
data(){
return{
users: []
}
},
mounted(){
this.checkUser()
},
methods:{
async checkUser(){
try {
console.log('System Users')
const response = await axios.get('superadmin/system-admins/');
this.users = response.data.results
console.log(response.data.results)
} catch (err) {
}
}
}
}
Did I miss something?
Related
In Vue 3, my component "App" makes an asynchronous API request to retrieve info about a "purchase". This "purchase" is passed on to its child component "DeliveryInfo".
In "DeliveryInfo", I need to make another request based on the "customerId" property contained in the "purchase" prop. However, when "DeliveryInfo" receives the "purchase" prop, it's value is at first undefined. The second API request would then fail.
To avoid this, I used a watcher, so that when "DeliveryInfo" eventually gets the content of the "purchase" prop, it would then make a call to the API and update its own data.
I heard this is not good for performance. Could someone help me to improve my code ?
Here's my App.vue component :
<template>
<Purchase :purchase="purchase" />
</template>
<script>
import Purchase from "./components/Purchase"
export default {
name: "App",
components: { Purchase },
data() {
return {
purchase: []
};
},
methods: {
async fetchPurchase(id) {
const response = await fetch(`myapi.com/purchases/${id}`);
const data = await response.json();
return data;
}
},
async created() {
// example with a specific id
this.purchase = await this.fetchPurchase(79886);
}
}
</script>
And my DeliveryInfo component :
<template>
<div class="delivery-info embossed">
<div>
<h4>Adress</h4>
<p>{{ purchase.deliveryAdress }}</p>
<p>{{ purchase.deliveryCity }}</p>
</div>
<div>
<h4>Customer info</h4>
<p>{{ customerData.firstname }} {{customerData.lastname}}</p>
<p>{{ customerData.phone }}</p>
</div>
</div>
</template>
<script>
export default {
name: "DeliveryInfo",
props: ["purchase"],
data(){
return{
customerData: {}
}
},
methods:{
async fetchCustomer(){
if(!this.purchase){
return
}
const response = await fetch(`myapi/customers/${this.purchase.customerId}`)
this.customerData = await response.json()
}
},
watch : {
purchase(){
this.fetchCustomer()
}
}
}
</script>
I am new to vue and trying to build my first vue app using nuxtjs. My problem right now has to do with architecture and folder structure.
In my other non-vue apps I always have a "services" directory where I keep all my code that makes http requests.
example under my services folder I will have a auth.ts file that contains code that posts login credentials to my API. This file/class returns a promise which I access from within my store.
I am trying to do this with vue using nuxtjs but I realised I am unable to access the axios module from anywhere aside my .vue file.
This is an example of how my code is now:
<template>
...
</template>
<script lang="ts">
import Vue from 'vue'
import ActionBar from '../../components/ActionBar.vue'
export default Vue.extend({
components: { ActionBar },
data() {
return {
example: ''
},
methods: {},
mounted() {
this.$axios.$get('/examples').then((res) => {
this.examples = res.data;
})
}
})
</script>
<style>
...
</style>
I would like to move the axios calls to their own files in my services folder. How do I do this?
what you can do is create a file inside the ./store folder, let's imagine, ./store/products.js, that will create a products store, inside, simple getters, mutations and actions:
export const state = () => ({
products: [],
fetchingProducts: false,
})
export const getters = {
getAllProducts(state) {
return state.products
},
hasProducts(state) {
return state.products.length > 0
},
isFetchingProducts(state) {
return state.fetchingProducts
},
}
export const mutations = {
setInitialData(state, products) {
state.products = products
},
setLoadingProducts(state, isLoading) {
state.fetchingProducts = isLoading
},
}
export const actions = {
async fetchProducts(context, payload) {
context.commit('setLoadingProducts', true)
const url = `/api/example/${payload.something}`
const res = await this.$axios.get(url)
context.commit('setInitialData', res.data)
context.commit('setLoadingProducts', false)
},
}
then in your .vue file, you can now use the store as:
<template>
<div>
<div v-if="isFetchingProducts"> loading... </div>
<div v-else-if="!hasProducts">no products found</div>
<div v-else>
<ul>
<li v-for="product in allProducts" :key="product.id">
{{ product.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {
products: []
}
},
methods: {
...mapGetters({
isFetchingProducts: 'products/isFetchingProducts',
allProducts: 'products/getAllProducts',
hasProducts: 'products/hasProducts',
})
},
mounted() {
this.$store.dispatch('products/fetchProducts', {})
},
}
</script>
<style>
...
</style>
remember that:
to call a store action, you should use $store.dispatch()
to call a mutation, you should use $store.commit()
to call a getter, you should use $store.getter()
you can also use the Vuex helper mapGetters, mapActions and even mapMutations
You might also know that you can leverage the Plugins in Nuxt, that article has demo code as well so you can follow up really quick
I try to find a way to use vuex with reusable component which store data in a store. The thing is, I need the store to be unique for each component instance.
I thought Reusable module of the doc was the key but finally it doesn't seem to be for this purpose, or i didn't understand how to use it.
The parent component:
(the prop “req-path” is used to pass different URL to make each FileExplorer component commit the action of fetching data from an API, with that url path)
<template>
<div class="container">
<FileExplorer req-path="/folder/subfolder"></FileExplorer>
<FileExplorer req-path="/anotherfolder"></FileExplorer>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
import FileExplorer from "#/components/FileExplorer.vue";
export default {
components: {
FileExplorer
}
};
</script>
The reusable component:
<template>
<div class="container">
<ul v-for="(item, index) in folderIndex" :key="index">
<li>Results: {{ item.name }}</li>
</ul>
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
export default {
props: ["reqPath"],
},
computed: {
...mapState("fileExplorer", ["folderIndex"])
},
created() {
// FETCH DATA FROM API
this.$store
.dispatch("fileExplorer/indexingData", {
reqPath: this.reqPath
})
.catch(error => {
console.log("An error occurred:", error);
this.errors = error.response.data.data;
});
}
};
</script>
store.js where I invoke my store module that I separate in different files, here only fileExplorer module interest us.
EDIT : I simplified the file for clarity purpose but I have some other state and many mutations inside.
import Vue from 'vue'
import Vuex from 'vuex'
// Import modules
import { fileExplorer } from '#/store/modules/fileExplorer'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
fileExplorer,
…
}
})
#/store/modules/fileExplorer.js
import ApiService from "#/utils/ApiService"
export const fileExplorer = ({
namespaced: true,
state: {
folderIndex: {},
},
mutations: {
// Called from action (indexingData) to fetch folder/fil structure from API
SET_FOLDERS_INDEX(state, data) {
state.folderIndex = data.indexingData
},
actions: {
// Fetch data from API using req-path as url
indexingData({
commit
}, reqPath) {
return ApiService.indexingData(reqPath)
.then((response) => {
commit('SET_FOLDERS_INDEX', response.data);
})
.catch((error) => {
console.log('There was an error:', error.response);
});
}
}
});
I need each component to show different data from those 2 different URL, instead i get the same data in the 2 component instance (not surprising though).
Thanks a lot for any of those who read all that !
Module reuse is about when you are creating multiple modules from the same module config.
First, use a function for declaring module state instead of a plain object.
If we use a plain object to declare the state of the module, then that
state object will be shared by reference and cause cross store/module
state pollution when it's mutated.
const fileExplorer = {
state () {
return {
folderIndex: {}
}
},
// mutations, actions, getters...
}
Then, dynamically register a new module each time a new FileExplorer component is created and unregister that module before the component is destroyed.
<template>
<div class="container">
<ul v-for="(item, index) in folderIndex" :key="index">
<li>Results: {{ item.name }}</li>
</ul>
</div>
</div>
</template>
<script>
import { fileExplorer } from "#/store/modules/fileExplorer";
import store from "#/store/index";
var uid = 1
export default {
props: ["reqPath"],
data() {
return {
namespace: `fileExplorer${uid++}`
}
},
computed: {
folderIndex() {
return this.$store.state[this.namespace].folderIndex
}
},
created() {
// Register the new module dynamically
store.registerModule(this.namespace, fileExplorer);
// FETCH DATA FROM API
this.$store
.dispatch(`${this.namespace}/indexingData`, {
reqPath: this.reqPath
})
.catch(error => {
console.log("An error occurred:", error);
this.errors = error.response.data.data;
});
},
beforeDestroy() {
// Unregister the dynamically created module
store.unregisterModule(this.namespace);
}
};
</script>
You no longer need the static module registration declared at store creation.
export default new Vuex.Store({
modules: {
// fileExplorer, <-- Remove this static module
}
})
I have a page like this:
<template>
<div class="row flex">
{{posts.id}}
</div>
</template>
<script>
import axios from 'axios'
export default {
async asyncData ({ route }) {
let { data } = await axios.get('http://localhost:8000/api/v1/feeds/' + route.params.id + '/')
return {
posts: data
}
}
}
</script>
When I click link with hot reload (router-link), it display well. But when I reload this window, it appear in 1 seconds and disappear then.
Video: http://g.recordit.co/ht0a0K2X81.gif
Error Log:
How can I fix this?
Add a property to your data i.e dataLoaded: false. When your ajax request has finished, set this.dataLoaded = true. On your template add v-if="dataLoaded. This will mean the template data won't render until you're ready.
You could also do v-if="posts" as another way but I generally have a consistent dataLoaded prop available to do this.
Edit: I just looked at your example again and doing something like this would work:
<template>
<div class="row flex" v-if="posts">
{{posts.id}}
</div>
</template>
<script>
import axios from 'axios'
export default {
data () {
return {
posts: null
}
}
methods:{
loadPosts () {
return axios.get('http://localhost:8000/api/v1/feeds/' + this.$route.params.id + '/')
}
},
created () {
this.loadPosts().then(({data}) => {
this.posts = data
})
}
}
</script>
I've removed the async and just setting posts when the axios request returns it's promise. Then on the template, it's only showing posts is valid.
Edit
You can also use your original code and just add v-if="posts" to the div you have in your template.
I just don't understand, in list.vue I only trigger an list action that asynchronously alters state.list in updated hook. items is a computed property that only relies on state.list. Why would this lead to infinite updated events?
I've found that if I move the code in updated hook to watch option like this:
watch: {
'$route': function () {
this.$store.dispatch('list')
},
},
infinite problem would disappear. But this would trigger updated hook twice every time $route change is watched, which I also don't know Why.
Simple demo is here. Related code
// main.js
var Vue = require('vue')
var app = require('./App.vue')
new Vue(app).$mount('#app')
// App.vue
<template>
<div id="app">
<h1>list bug test</h1>
<router-view></router-view>
</div>
</template>
<script>
import store from './store.js'
import router from './router.js'
export default {
store,
router,
}
</script>
// list.vue
<template>
<table>
<tbody>
<tr>
<th>title</th>
<th>actions</th>
</tr>
<tr v-for="(item, index) in items">
<td> {{item.title}} </td>
<td> submit </td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
updated: function () {
console.log('items.vue updated')
this.$store.dispatch('list')
},
mounted: function () {
console.log('items.vue mounted')
this.$store.dispatch('list')
},
computed: {
items: function () {
return this.$store.state.list.map(e => ( {title: e.ksmc } ))
},
},
}
</script>
// router.js
var router = new VueRouter({
routes:[
{
path:'/',
name: 'list',
component: listView,
},
],
})
// store.js
var store = new Vuex.Store({
state: {
error: undefined,
list: JSON.parse(localStorage.getItem('list')) || [],
},
mutations: {
list: function(state, list) {
state.list = list
},
error: function(state, error) {
state.error = error
},
},
actions: {
list (ctx, kwargs) {
setTimeout(() => {
ctx.commit('list', [{ksmc:'this is a title'}])
}, 1000)
},
},
})
The updated hook is called after the component's DOM has been updated due to a change in the component's data model (to cause the component to be re-rendered). Therefore you shouldn't change the component's state (async or not) inside this hook otherwise the state change will cause the component to re-render which will fire the updated hook which will change the state... and so on.
The docs explain it well:
The component’s DOM will have been updated when this hook is called, so you can perform DOM-dependent operations here. However, in most cases you should avoid changing state inside the hook. To react to state changes, it’s usually better to use a computed property or watcher instead.