I'm using vueJs MDB-datatable to display my data coming from my API.
I followed the MDB-datable documentation in handling the "OtherJSON structure" but it didn't re-render the data from the API request.
I tried different callback beforeCreate, created, beforeMount, and mounted, the data was changed but still, it didn't render the latest data.
Here's the code:
<template>
<mdb-datatable
:data="tableData"
striped
bordered
/>
</template>
<script>
import 'mdbvue/build/css/mdb.css';
import { mdbDatatable } from 'mdbvue';
export default {
components: {
mdbDatatable
},
data: () => ({
tableData: {
columns: [],
rows: []
}
}),
created() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => {
let keys = ["id", "name", "username"];
let entries = this.filterData(json, keys);
//columns
this.tableData.columns = keys.map(key => {
return {
label: key.toUpperCase(),
field: key,
sort: 'asc'
};
});
console.log(this.tableData.columns);
//rows
entries.map(entry => this.tableData.rows.push(entry));
console.log(this.tableData.rows);
})
.catch(err => console.log(err))
},
methods: {
filterData(dataArr, keys) {
let data = dataArr.map(entry => {
let filteredEntry = {};
keys.forEach(key => {
if(key in entry) {
filteredEntry[key] = entry[key];
}
})
return filteredEntry;
})
return data;
}
}
</script>
The MDB-datatable documentation seems to be straight forward but I don't know which part I'm missing.
I'm new to VueJS. Any help is much appreciated.
It seems that the current version of MDB Vue (5.5.0) takes a reference to the rows and columns arrays and reacts when these arrays mutate rather than reacting to changes to the property bound to the data prop itself.
I see you are already mutating rather than replacing the rows array, so you need to do the same with the columns array.
//columns
this.tableData.columns.push(...keys.map(key => {
return {
label: key.toUpperCase(),
field: key,
sort: 'asc'
};
}));
Related
const myArray =[];
const nestedMenuitems = ref([
{
items:myArray
},
]);
onMounted(() => {
ApiService.getStores().then((data) => {
stores.value = data;
stores.value.map(function(value, _key) {
myArray.push({lable: value.shop});
});
});
});
i am trying show array data items:myArray this line
under the code working fine but mu custom data api to pass data its not working.what is the problem.
how to solve this?
const list= [
{
label: 'Tracker',
icon: 'pi pi-fw pi-compass'
},
{
label: 'Map',
icon: 'pi pi-fw pi-map-marker'
},
{
label: 'Manage',
icon: 'pi pi-fw pi-pencil'
}
];
const nestedMenuitems = ref([
{
items:list
},
]);
i am trying show array data items:myArray this line
under the code working fine but mu custom data api to pass data its not working.what is the problem.
how to solve this?
<Menubar :model="nestedMenuitems" optionValue="value">
</Menubar>
model="nestedMenuitems to get dropdown data
To trigger reactivity you need to replace the item inside nestedMenuItems.
What's not clear to me is why aren't you using a simpler data structure:
const state = reactive({ items: [] });
const { items } = toRefs(state);
onMounted(() => {
ApiService.getStores().then((data) => {
data.forEach((value) => {
state.items.push({ label: value.shop });
});
});
});
What do you need nestedMenuItems for? Do you have more than one nestedMenuItem?
You might want to show <Menubar />. Most likely, you don't need v-model in it, but v-bind. e.g:
<Menubar :items="items" optionValue="value" />
And another question would be: if you're storing the response in stores, why are you also storing it somewhere else (e.g: in nestedMenuItems, or items)? Why not consume it directly from stores?
For example:
const stores = ref([]);
const items = computed(() => stores.value.map(({ shop: label }) => ({ label })));
onMounted(() => {
ApiService.getStores().then((data) => {
stores.value = data;
});
});
<pre v-text="JSON.stringify({ stores, items }, null, 2)" />
It's bad practice to keep the same reactive data in two separate places, because you're always running the risk of them getting out of sync. If you're using the same source, any change will be reflected in every place where the data is consumed/displayed.
I have a couple of Pinia stores that should share a set of actions and getters and I’m not quite sure how to effectively achieve that.
I’m building an app that lets users manage a number of different media (Books, Movies, TV Shows, etc). The way I’m currently thinking about it is to have a store for each media type e.g. BookStore, MovieStore etc. A lot of the getters and actions (e.g., count and deleteOne) are exactly the same between those different stores.
How do I achieve DRY here? The Pinia documentation has examples that mostly focus around reusing actions and getters inside other stores but I don’t think that quite solves my use case of inheriting a set of getters and setters outright.
Is my attempted inheritance approach here an anti-pattern?
This is achievable using plugins docs
Example Movies:
You have multiple stores using shared naming scheme for each state:
item: single entity item (single movie details)
collection: collection of items (collection of all movies)
each store will have the same CRUD actions with only the URL changing
getCollection: get list of items from API and set response as collection (https://url.com/movies)
getItem: get single item from API and set response as item (https://url.com/movies/id)
handleError: displays alert to the user with error information
Create plugin:
function BaseStorePlugin () {
return {
collection: [],
item: {},
getCollection: function (url) {
api.get(url)
.then((response) => {
this.collection = response.data;
})
.catch((error) => {
this.handleError(error);
});
},
getItem: function (url) {
api.get(url)
.then((response) => {
this.item = response.data;
})
.catch((error) => {
this.handleError(error);
});
},
handleError: function (error) {
window.alert(error);
},
};
}
Give plugin to Pinia:
const pinia = createPinia();
pinia.use(BaseStorePlugin);
Example movieStore.js (using shared action & state)
import { defineStore } from 'pinia';
import { api } from 'src/boot/axios';
export const useMovieStore = defineStore({
id: 'movie',
state: () => ({
movieSpecificStateObject: {},
}),
actions: {
movieSpecificAction (url) {
console.log(this.item);
api.get(url)
.then((response) => {
// handle response
})
.catch((error) => {
this.handleError(error);
});
},
},
});
Example usage in component
<template>
<div
v-for="movie in movieStore.collection"
:key="movie.id"
>
<div>
{{ movie.name }}
</div>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
import { useMovieStore } from 'src/stores/movieStore.js';
const movieStore = useMovieStore();
onMounted(() => {
movieStore.readCollection('http://url.com/movies');
});
</script>
Edit: 1
if you pass the context into the plugin you have access to the store and options being passed into it, from this you could check the store id and only return for specific stores like below
function BaseStorePlugin (context) {
const allowedStores = ['movie', 'album'];
if (allowedStores.includes(context.store.$id)) {
return {
collection: [],
getCollection: function () {
const fakeCollection = Array.from({length: 10}, () => Math.floor(Math.random() * 40));
fakeCollection.forEach((item) => {
this.collection.push({
id: item,
name: `name${item}`
});
});
},
};
};
}
I have created a very basic example using 3 stores and the above check available on codesandbox here
In the below code snippet, if i remove .filter(beer => beer.isFavourite) the computed property works with no error, essentially becoming the same value as allBrewDogBeers. When i add filter, i get "TypeError: this.allBrewDogBeers.filter is not a function". I think maybe this is to do with the context, but i don't really understand what i'm missing, because i've written a filter as simple as this before, but maybe Vue is confusing me.
I just want to have a computed property which is a subset of allBrewDogBeers, I v-bind this computed property to a favourite component in my template.
computed: {
favouriteBeers: function() {
return this.allBrewDogBeers.filter(beer => beer.isFavourite);
}
},
full script:
<script>
import { eventBus } from './main.js'
import BeerList from './components/BeerList.vue'
import BeerDetail from './components/BeerDetail.vue'
import FavouriteBeers from './components/FavouriteBeers.vue'
export default {
name: "App",
components: {
'beer-list': BeerList,
'beer-detail': BeerDetail,
'favourite-beers': FavouriteBeers
},
data() {
return {
allBrewDogBeers: [],
selectedBeer: null
};
},
computed: {
favouriteBeers: function() {
return this.allBrewDogBeers.filter(beer => beer.isFavourite);
}
},
methods:{
addFavourBeer: function(beer) {
const index = this.allBrewDogBeers.indexOf(beer);
this.allBrewDogBeers[index].isFavourite = true;
},
removeFavourBeer: function(beer) {
const index = this.allBrewDogBeers.indexOf(beer);
this.allBrewDogBeers[index].isFavourite = false;
}
},
mounted(){
fetch("https://api.punkapi.com/v2/beers")
.then(response => this.allBrewDogBeers = response.json())
.then(data => this.allBrewDogBeers = data);
eventBus.$on('beer-selected', (beer) => {this.selectedBeer = beer});
eventBus.$on('beer-favourite-add', beer => this.addFavourBeer(beer));
eventBus.$on('beer-favourite-remove', beer => this.removeFavourBeer(beer));
}
};
</script>
You've got a problem here:
.then(response => this.allBrewDogBeers = response.json())
That's assigning a promise to this.allBrewDogBeers, which isn't what you want.
Change it to:
.then(response => response.json())
The other then will handle the assignment.
I'm trying to get data from a API that i created, this data is listed just as i wanted, but i can't manipulate this data because it returns a [ob: Observer]. How can i extract data from this ?
Store.js:
items: state => {
item.getAll().then(response => {
state.items = response.data
})
return state.items
},
Component.vue:
...mapGetters(["items"]),
<v-card flat v-for="item in items" :key="item.title">
Just as #skirtle commented i needed to move my function to actions because i'm using a async task.
My final code ended up that way:
getters: {
items: state => state.items,
...
}
mutations: {
'LOAD_ITEMS'(state, data) {
const items = []
for (let key in data) {
const item = data[key];
item.id = key;
items.push(item);
}
state.items = items;
...
}
actions: {
loadItems({ commit, state }) {
...
return axios.get("item.json")
.then(response => {
commit("LOAD_ITEMS", response.data);
}).catch(error => console.log(error));
}
}
I'm using firebase, so i did a loop to store the key.
If you has your own API maybe you can just store response.data directly in a JS object without using a loop.
I've several components using vue-tables-2 but one of them is not updating the table until I change the route.
component
<template>
//..
<div class="table-responsive" >
<v-client-table ref="table" name="vCardTable"
:data="vCardTableData.data"
:columns="vCardTableData.headers"
:options="vCardTableData.options"/>
</div>
//..
</template>
<script>
import { mapState } from "vuex";
import { mapGetters } from "vuex";
export default {
name: "VCard",
computed: {
...mapState("commons", ["user"]),
...mapGetters({ vCardTableData: "vCard/vCardTableData" })
},
mounted() {
var self = this;
self.$nextTick(() => {
self.$store.dispatch("vCard/getVCards"); <-- GET TABLE DATA
});
}
};
</script>
store
const state = {
vCardTableData: {
data: [],
headers: [
//..
],
options: {
filterable: false,
preserveState: false,
headings: {
//..
},
texts: {
//..
},
pagination: {
dropdown: true,
},
templates: {
//..
},
},
}
}
const getters = {
vCardTableData: state => state.vCardTableData
}
const actions = {
getVCards({commit, dispatch}) {
return api.request("get", "getvcards").then(response => {
setTimeout(() => {
commit("setVCardTableData", response.data.vcards);
}, 300);
}).catch(error => {
console.log(error);
});
}
}
const mutations = {
clearTableData: (state) => {
if (state.vCardTableData.data) {
state.vCardTableData.data = [];
}
},
setVCardTableData : (state, vCardTableData) => state.vCardTableData.data = vCardTableData
}
As you can see in this image the table has data:
But the view is refreshed when the route changes:
02/05/2018
Well now I've seen that if I modify the state directly in the component with promises it works:
this.$store.dispatch("vCard/getVCards", []).then((responseData)=>{
this.$store.state.vCard.vCardTableData.data = responseData;
});
Does anyone know why?
Thank you
My last answer was wrong, I did not remember that I had changed the vuex parameter of the table to false. I don't know why but doing a push it works:
setVCardTableData : (state, vCardTableData) => {
vCardTableData.forEach(tableData => {
state.vCardTableData.data.push(tableData);
});
}
This is a probably a reactivity issue. (See https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats for detailed explanation.)
Changing how you set the object value in your mutation to this should solve the problem
setVCardTableData: (state, vCardTableData) => state.vCardTableData = {
...state.vCardTableData,
data: vCardTableData
}
Basically, this creates a new object so that Vue knows that the object has been updated. In Javasript, object is passed by reference, meaning that vCardTableData don't store the object, it stores the reference to the object. You could think of it as a pointer/address that points to the object in memory. When you change a child property in the object, the reference remains unchanged, so Vue does not know that the object has been updated. Creating a new object makes sure that the object reference is updated.
This is also explained in Mutations Follow Vue's Reactivity Rules
in https://vuex.vuejs.org/en/mutations.html
I have had similar issues. Like others have already mention it is probably a reactivity problem. You can use Vue.set() to ensure that your properties are reactive when setting the state values.
setVCardTableData: (state, vCardTableData) => {
Vue.set(state.vCardTableData, 'data', vCardTableData);
}
Check the official documentation for this method.
Since it's a deep object, you need to use Object.assign in your mutation
setVCardTableData: (state, vCardTableData) => Object.assign(state.vCardTableData.data, vCardTableData)