Vue - composition API Array inside reactive is not updating in the DOM - javascript

I have an Array of posts inside reactive() and I want it to be updated onMounted.
How can I do this?
TEMPLATE:
<q-card>
<board-item-list :items="items" v-if="items.length" />
<board-empty v-else />
</q-card>
SCRIPT
import { reactive, onMounted } from "vue";
import { posts } from "./fake-data.js";
export default {
setup() {
let items = reactive([]);
...
onMounted(() => {
// to fill the items with posts.
items.values = posts; // I tried this not working
items = posts; //I tried this not working
console.log(items);
});
...
return {
...
items,
};
},
};

Try to use ref instead of reactive or define items as nested field in a reactive state like :
import { reactive, onMounted } from "vue";
import { posts } from "./fake-data.js";
export default {
setup() {
let state= reactive({items:[]});
...
onMounted(() => {
state.items = posts;
console.log(state.items);
});
...
return {
...
state,
};
},
};
in template :
<q-card>
<board-item-list :items="state.items" v-if="state.items.length" />
<board-empty v-else />
</q-card>
if you want to get rid of state in the template you could use toRefs:
import { reactive, onMounted,toRefs } from "vue";
import { posts } from "./fake-data.js";
export default {
setup() {
let state= reactive({items:[]});
...
onMounted(() => {
state.items = posts;
console.log(state.items);
});
...
return {
...toRefs(state),//you should keep the 3 dots
};
},
};

composition api is very frustrating, vue2 was much better.
composition api is much more complex with basically no tangible benefit
composition api has so many reactivity problems like the above, proxy objects, pointless wrapper, total waste of space imho, vue3 is a great example of developers wrecking a good project.

Related

Vue composable - how to use multiple instances of composable without sharing state?

I have a composable file in my VueJS application:
// simple example of a composable that fetch some information from API and shows that information
// in a table in two components at the sime time
export function useDatatable () {
const table = ref({
headers: [...],
items: [],
someValue: ''
})
async function getDocuments () {
const { data } = await $axios.get('/documents')
table.value.items = data
}
return {
table,
getDocuments
}
}
Then I have multiple components that use this composable at the same time:
<template>
<div>
<document-table /> // composable is used here
<document-billing-dialog /> // composable is used here too
</div>
</template>
Then in both components (document-table and document-billing-dialog) I use this composable like this:
<template>
<div>
{{ table.someValue }}
</div>
<v-table :items="table.items" />
<v-btn #click="getDocuments">
Reload table
</v-btn>
// other components
</template>
<script>
import { useDatatable } from '~/composables/useDatatable'
// other imports
export default defineComponent({
setup () {
const { table, getDocuments } = useDatatable()
onMounted(() => { getDocuments() })
return {
table,
getDocuments
}
}
})
</script>
However when 1 component calls the getDocuments function it gets called twice because its being used in two components at the same time.
Other example is that if I change the value of table.value.someValue = 'something' it changes in both components.
Is there any way to have multiple instances of a composable at the same time without sharing the state?

Make shared property reactive in Vue Composition API composable by declaring variable outside of exported function

I am using the composition api plugin for vue2 (https://github.com/vuejs/composition-api) to reuse composables in my app.
I have two components that reuse my modalTrigger.js composable, where I'd like to declare some sort of shared state (instead of using a bloated vuex state management).
So in my components I do something like:
import modalTrigger from '../../../../composables/modalTrigger';
export default {
name: 'SearchButton',
setup(props, context) {
const { getModalOpenState, setModalOpenState } = modalTrigger();
return {
getModalOpenState,
setModalOpenState,
};
},
};
And in my modalTrigger I have code like:
import { computed, ref, onMounted } from '#vue/composition-api';
let modalOpen = false; // needs to be outside to be accessed from multiple components
export default function () {
modalOpen = ref(false);
const getModalOpenState = computed(() => modalOpen.value);
const setModalOpenState = (state) => {
console.log('changing state from: ', modalOpen.value, ' to: ', state);
modalOpen.value = state;
};
onMounted(() => {
console.log('init trigger');
});
return {
getModalOpenState,
setModalOpenState,
};
}
This works, but only because I declare the modalOpen variable outside of the function.
If I use this:
export default function () {
const modalOpen = ref(false); // <------
const getModalOpenState = computed(() => modalOpen.value);
...
It is not reactive because the modalTrigger is instantiated twice, both with it's own reactive property.
I don't know if that is really the way to go, it seems, that I am doing something wrong.
I also tried declaring the ref outside:
const modalOpen = ref(false);
export default function () {
const getModalOpenState = computed(() => modalOpen.value);
But this would throw an error:
Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function.
So what would be the correct way to achieve this?
I somehow expected Vue to be aware of the existing modalTrigger instance and handling duplicate variable creation itself...
Well, anyway, thanks a lot in advance for any hints and tipps.
Cheers
Edit:
The complete header.vue file:
<template>
<header ref="rootElement" :class="rootClasses">
<button #click="setModalOpenState(true)">SET TRUE</button>
<slot />
</header>
</template>
<script>
import { onMounted, computed } from '#vue/composition-api';
import subNavigation from '../../../../composables/subNavigation';
import mobileNavigation from '../../../../composables/mobileNavigation';
import search from '../../../../composables/searchButton';
import { stickyNavigation } from '../../../../composables/stickyNav';
import metaNavigation from '../../../../composables/metaNavigation';
import modalTrigger from '../../../../composables/modalTrigger';
export default {
name: 'Header',
setup(props, context) {
const { rootElement, rootClasses } = stickyNavigation(props, context);
mobileNavigation();
subNavigation();
search();
metaNavigation();
const { getModalOpenState, setModalOpenState } = modalTrigger();
onMounted(() => {
console.log('Header: getModalOpenState: ', getModalOpenState.value);
setModalOpenState(true);
console.log('Header: getModalOpenStat: ', getModalOpenState.value);
});
return {
rootClasses,
rootElement,
getModalOpenState,
setModalOpenState,
};
},
};
</script>
The composition API is setup somewhere else where there are Vue components mounted a bit differently than you normally would.
So I can't really share the whole code,but it has this inside:
import Vue from 'vue';
import CompositionApi from '#vue/composition-api';
Vue.use(CompositionApi)
The composition API and every other composable works just fine...

Simple Vue Axios example from docs, but using a component

This is probably a very silly question, but believe me I have tried hard to figure it out, to no avail.
I have an appService.js file where I call an API like so:
import axios from 'axios'
axios.defaults.baseURL = 'https://www.alphavantage.co'
const appService = {
getPosts() {
axios.get(`/query?function=TIME_SERIES_INTRADAY&symbol=MSFT&interval=5min&apikey=xxx`)
.then(response => this.info = response)
}
}
export default appService
and then I have a Vue component (Stocks.vue) where I want to display {{ info }} like so:
<template>
<div>
<h4>{{ info }}</h4>
</div>
</template>
<script>
import appService from '../app.service.js'
export default {
name: 'Stocks',
props: {
msg: String
},
}
</script>
I literally just want to dump everything I get from the API in that tag. I will figure the rest out later.
I am basically doing the simple Axios example from the Vue docs, but using a component instead. (https://v2.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html#Base-Example)
Hope that makes sense!
Thanks in advance
You'll need to change your appService function to return the promise created by axios.get. You also can't assign values to this in the function, but you can in your component.
export default {
getPosts () {
return axios.get('/query', {
params: { // dealing with a params object is easier IMO
function: 'TIME_SERIES_INTRADAY',
symbol: 'MSFT',
interval: '5min',
apikey: 'xxx'
}
})
}
}
then in your component, perhaps in the created hook
data () {
return {
info: {} // or some other appropriate default value
}
},
async created () {
this.info = await appService.getPosts()
}

vuex store doesn't update component

I'm new to vue, so I'm probably making a rookie error.
I have a root vue element - raptor.js:
const Component = {
el: '#app',
store,
data: {
productList: store.state.productlist
},
beforeCreate: function () {
return store.dispatch('getProductList', 'getTrendingBrands');
},
updated: function (){
console.log(111);
startSlider();
}
};
const vm = new Vue(Component);
Using this template
<div class="grid-module-single popular-products" id="app">
<div class="row">
<div class="popular-items-slick col-xs-12">
<div v-for="product in productList">
...
</div>
</div>
</div>
My store is very simple store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import model from '../../utilities/model';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
productlist: []
},
mutations: {
setProductList(state, data) {
state.productlist = data;
}
},
actions: {
getProductList({ commit }, action) {
return model.products().then(data => commit('setProductList', data));
}
}
});
In my vuex devtool, I can see, that the store is being updated
https://www.screencast.com/t/UGbw7JyHS3
but my component is not being updated:
https://www.screencast.com/t/KhXQrePEd
Question:
I can see from the devtools, that my code is working. The store is being updated with data. My component is not being updated,however. I thought it was enough just to add this in the data property on the component:
data: {
productList: store.state.productlist
}
but apparently the data object doesn't seem to be automatically synced with the store. So either I'm doing a complete vue no-no somewhere, or I need to tweak the code a bit. Anyway can anyone help me in the right direction.
Thanks a lot.
UPDATE
Figured it out myself. Just had to replace the components data part with a computed method:
data:
data: {
productList: store.state.productlist
}
and replace it with.
computed: {
productList () {
return store.state.productlist;
}
},
data only work once on component before render, so you can use computed instead.
like above answer, or you can use mapstate
import {mapState} from 'vuex'
...
computed: mapState({
productList: state => state.productList
})
First - use getter to do this mapGetters, also you need to watch this property somehow, you can set store subscription or just with watch method trough component.
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'UPDATE_DATA') {
...
}
}
You are calling the store into the productList data property in the wrong way.
You can try it:
data: {
productList: $store.state.productlist
}
Otherwise you have to import store in each component that are using the store.

How to design a store in Vuex to handle clicks in nested, custom components?

I'm trying to design a store to manage the events of my Vuex application. This far, I have the following.
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = { dataRows: [], activeDataRow: {} };
const mutations = {
UPDATE_DATA(state, data) { state.dataRows = data; state.activeDataRow = {}; },
};
export default new Vuex.Store({ state, mutations });
I'm going to have a number of list items that are supposed to change the value of the data in the store when clicked. The design of the root component App and the menu bar Navigation is as follows (there will be a bunch of actions in the end so I've collected them in the file actions.js).
<template>
<div id="app">
<navigation></navigation>
</div>
</template>
<script>
import navigation from "./navigation.vue"
export default { components: { navigation } }
</script>
<template>
<div id="nav-bar">
<ul>
<li onclick="console.log('Clickaroo... ');">Plain JS</li>
<li #click="updateData">Action Vuex</li>
</ul>
</div>
</template>
<script>
import { updateData } from "../vuex_app/actions";
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData }
}
}
</script>
Clicking on the first list item shows the output in the console. However, when clicking on the second one, there's nothing happening, so I'm pretty sure that the event isn't dispatched at all. I also see following error when the page's being rendered:
Property or method "updateData" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
I'm very new to Vuex so I'm only speculating. Do I need to put in reference to the updateData action in the store, alongside with state and mutations? How do I do that? What/where's the "data option" that the error message talks about? Isn't it my components state and it's properties?
Why the error
You are getting the error, because when you have <li #click="updateData"> in the template, it looks for a method updateData in the vue component which it does not find, so it throws the error. To resolve this, you need to add corresponding methods in the vue component like following:
<script>
import { updateData } from "../vuex_app/actions";
export default {
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData }
},
methods:{
updateData: () => this.$store.dispatch("updateData")
}
}
</script>
What this.$store.dispatch("updateData") is doing is calling your vuex actions as documented here.
What/where's the "data option"
You don't have any data properties defined, data properties for a vue component can be used, if you want to use that only in that component. If you have data which needs to be accessed across multiple components, you can use vuex state as I believe you are doing.
Following is the way to have data properties for a vue component:
<script>
import { updateData } from "../vuex_app/actions";
export default {
date: {
return {
data1 : 'data 1',
data2 : {
nesteddata: 'data 2'
}
}
}
vuex: {
getters: { activeDataRow: state => state.activeDataRow },
actions: { updateData }
},
methods:{
updateData: () => this.$store.dispatch("updateData")
}
}
</script>
You can use these data properties in the views, have computed properies based on it, or create watchers on it and many more.

Categories

Resources