I just tryed to transform my Component into a Composition API component and realised that the parameter for the exported function dosn't get updated.
this is my Component:
export default {
name: "ListItem",
components: {
ListItemButton,
InputSwitch
},
props:{
item:{
type: Array,
required: true
},
dragable:{
type: Boolean,
required: true
},
dontShowButtons:{
type: Boolean,
default: false
},
dragItem:{
type: Array,
default: []
},
deaktivierbar:{
type: Boolean
}
},
setup(props, context){
const buttonClicked = ref(false)
const editMode = ref(false)
const itemName = ref("")
const itemDescription = ref("")
const canDrag = ref(false)
const activeState = ref(true)
onMounted(()=>{
canDrag.value = props.dragable
itemName.value = props.item[0]
itemDescription.value = props.item[1]
})
const { dragStart, dragEnd, checkForDragging, isDragging, showPlaceholder} = useDragFunction(props.dragItem)
return{
//Variables
buttonClicked,
isDragging,
editMode,
itemName,
itemDescription,
canDrag,
showPlaceholder,
activeState,
//functions
dragStart,
dragEnd,
}
}
and this is my exported function:
import { ref, computed, watch } from 'vue';
export default function useDragFunction( dragItem){
const isDragging = ref(false)
const showPlaceholder = ref(false)
function dragStart(){
isDragging.value = true
}
function dragEnd(){
isDragging.value = false
}
function checkForDragging(){
if (dragItem.length >0){
showPlaceholder.value = true
}
else{
showPlaceholder.value = false
}
}
return{
dragStart,
dragEnd,
checkForDragging,
isDragging,
dragItem,
showPlaceholder
}
}
so when the user starts dragging, the function "checkForDragging()" gets executed. Also the props of the component changes so that the draged Item is stored in the dragItem variable. if i write everything into the component it works fine. if i export it into that function it stopped working. i found out that the passed property "dragItem" is always "[]". the props value will not get updated. i literally tryed everything but i cant get it to work.
If a prop is expected to be changed, it should be passed not by value but by reference, i.e. as a ref:
const dragItem = computed(() => props.dragItem);
const {...} = useDragFunction(dragItem);
And used like:
if (dragItem.value.length >0){ ...
Depending on the case, checkForDragging is redundant, and showPlaceholder needs to computed from dragItem:
const showPlaceholder = computed(() => dragItem.value.length >0);
Related
I can't get multiple components accessing the same store respond to updates, until I mess with a dom element to trigger new render.
In my Pinia store, I have an array, and an update method:
let MyArray: IMyItem[] = [
{ id: 1,
name: "First Item",
}, ....
let SelectedItem = MyArray[0]
const AddItem = ( n: string, ) => {
MyArray.push({ id: createId(), name: n, });
};
return { MyArray, SelectedItem, AddItem }
In one Vue component, I have text inputs and a button to call the store's method:
function handle() {store.AddItem(name.value));
In another Vue component, on the same parent, I use a for loop to display allow for selecting an item:
<div v-for="item in store.MyArray">
<input type="radio"...
No changes with these efforts:
const { MyArray } = storeToRefs(store);
const myArray = reactive(store.MyArray);
// also watching from both components...
watch(store.MyArray, (n, o) => console.dir(n));
// also... lots of other stuff.
const myArray = reactive(store.MyArray);
watch(myArray, (n, o) => console.dir(n));
I also experimented with <form #submit.prevent="handle"> triggering nextTick by adding a string return to the store's method.
I assume the reason clicking around makes it work is because I'm changing the the store's SelectedItem, and its reactivity calls for re-rendering, as it is v-model for a label.
The docs say Array.push should be doing it's job... it just isn't bound the same way when used in v-for.
What's needed to trigger the dom update? Thanks! π©
As comments pointed out, the main issue is your store state is not declared with the Reactivity API, so state changes would not trigger watchers and would not cause a re-render.
The solution is to declare MyArray as a reactive and SelectedItem as a ref:
// store.js
import { defineStore } from 'pinia'
import type { IMyItem } from './types'
import { createId } from './utils'
π π
import { ref, reactive } from 'vue'
export const useItemStore = defineStore('item', () => {
π
let MyArray = reactive([{ id: createId(), name: 'First Item' }] as IMyItem[])
π
let SelectedItem = ref(MyArray[0])
const AddItem = (n: string) => {
MyArray.push({ id: createId(), name: n })
}
return { MyArray, SelectedItem, AddItem }
})
If using storeToRefs(), make sure to set the ref's .value property when updating the SelectedItem:
// MyComponent.vue
const store = useItemStore()
const { SelectedItem, MyArray } = storeToRefs(store)
const selectItem = (id) => {
π
SelectedItem.value = MyArray.value.find((item) => item.id === id)
}
But in this case, it's simpler to use the props off the store directly:
// MyComponent.vue
const store = useItemStore()
const selectItem = (id) => {
store.SelectedItem = store.MyArray.find((item) => item.id === id)
}
demo
I am using Quasar v2, using the Vue Composition API and vue-i18n, and I would like the site title to change display when the active language changes (via a drop down), but whatever I am trying does not result in the title language being changed. Any ideas?
Below is what I have right now (just the essentials):
import { defineComponent, ref, computed } from 'vue';
import { useMeta } from 'quasar';
export default defineComponent({
setup () {
const { t: translate } = useI18n() as any;
const siteTitle = computed(() => translate('title.app') as string);
const pageMetadata = {
title: 'untitled',
titleTemplate: (title: string) => `${title} - ${siteTitle.value}`
};
useMeta(pageMetadata);
}
});
The code I am using to switch languages:
async onChangeLanguage () {
try {
let locale = this.language;
if (this.language === 'en') {
locale = 'en-GB';
}
this.$i18n.locale = locale;
const quasarLang = await import(`quasar/lang/${locale}`);
if (quasarLang) {
Quasar.lang.set(quasarLang.default);
}
} catch (error) {
this.$log.error(error);
}
}
According to the documentation, useMeta will not be reactive if you pass a simple object to it. Rather, you should pass a function that returns the desired value:
export default defineComponent({
setup () {
const { t: translate } = useI18n() as any;
const siteTitle = computed(() => translate('title.app') as string);
useMeta(() => {
const title = 'untitled';
const titleTemplate = `${title} - ${siteTitle.value}`
return { title, titleTemplate }
});
});
I am writing an app with vuejs and I want to load data when component is mounted but setup returns data before onMounted loads data
My Code
I got the id from another component through props. then passed it through a function which contains an axios method.
The returns an array of data.
export default defineComponent({
props: {
courseId: {
type: String
}
},
data(){
return {
courseDetails: []
}
},
setup(props) {
let courseDetail = reactive([])
let sections = reactive([])
console.log("No details", courseDetail)
onMounted(() => {
// showLoader(true);
Course.getSectionAndSubsections("get-section-and-subsections", props.courseId).then(response => {
courseDetail = response.data.courses;
sections = response.data.sections;
console.log("leave my house", courseDetail)
NotificationService.success(response.data.message);
// showLoader(false);
}).catch(err => {
NotificationService.error(err.response);
// showLoader(false);
});
});
console.log("Course detail ", courseDetail);
return{
courseDetail
}
}
})
How can I solve this problem?
I want to load data when component is mounted but setup returns data
before onMounted loads data
That's correct - before onMounted hook it should return default values. The problem is that your data is not reactive. You are using reactive like that:
let courseDetail = reactive([])
courseDetail = response.data.courses;
And that is incorrect, because you don't change couseDetail content. You assign a new object.
Probably ref would be a little better in this situation:
const courseDetail = ref([])
And then you can get value:
console.log(courseDetail.value)
and assign a new one:
courseDetail.value = SOMETHING
What you did is:
let courseDetail = reactive([])
courseDetail = response.data.courses;
It can work but it should look like this:
let state = reactive({
courseDetail: []
})
state.courseDetail = response.data.courses;
Check my demo with two options: based on ref and based on reactive:
https://codesandbox.io/s/ref-vs-reactive-jfdvc?file=/src/App.vue
I have a store which is just an array of strings.
I am trying to watch it and do a search when it has changed.
Originally I had a computed value a bit like this:
const { value } = computed(() => {
const urls = store.getters.wishlist;
filters.value = createFilters("IndexUrl", urls);
return useListProducts(page.value, filters.value);
});
which I returned like this:
return { ...value, skip, more };
This worked fine when loading the page the first time, but if another component adds/removes something from the wishlist I want the function to fire again.
For context, here is the whole component:
import {
computed,
defineComponent,
getCurrentInstance,
ref,
} from "#vue/composition-api";
import Product from "#components/product/product.component.vue";
import {
createFilters,
createRequest,
useListProducts,
} from "#/_shared/logic/list-products";
export default defineComponent({
name: "Wishlist",
components: { Product },
setup() {
const instance = getCurrentInstance();
const store = instance.proxy.$store;
const page = ref(1);
const skip = ref(0);
const filters = ref([]);
const { value } = computed(() => {
const urls = store.getters.wishlist;
filters.value = createFilters("IndexUrl", urls);
return useListProducts(page.value, filters.value);
});
const more = () => {
skip.value += 12;
page.value += 1;
const request = createRequest(page.value, filters.value);
value.fetchMore({
variables: { search: request },
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return {
search: {
__typename: prev.search.__typename,
hasMoreResults: fetchMoreResult.search.hasMoreResults,
total: fetchMoreResult.search.total,
facets: [...prev.search.facets, ...fetchMoreResult.search.facets],
items: [...prev.search.items, ...fetchMoreResult.search.items],
},
};
},
});
};
return { ...value, skip, more };
},
});
So I figured that the issue was that I wasn't actually watching anything, so I removed the computed method and instead decided to setup a watch. First I created a listProducts method:
const result = reactive({
result: null,
loading: null,
error: null,
fetchMore: null,
});
const listProducts = (urls: string[]) => {
console.log(urls);
filters.value = createFilters("IndexUrl", urls);
Object.assign(result, useListProducts(page.value, filters.value));
};
And then I invoked that in my setup:
listProducts(store.getters.wishlist);
Then I setup a watch:
watch(store.getters.wishlist, (urls: string[]) => listProducts(urls));
What I expected to happen, was that when an item was added/remove from the wishlist store, it would then invoke listProducts with the new set of urls. But it didn't fire at all.
Does anyone know what I am doing wrong?
I believe the issue is with destructuring the reactive property, on destructuring you assign the properties to variables and no longer have a proxy to react to changes..try
return { value, skip, more };
and reference the property in your template
<template>
{{value.foo}}
</template
this question has to do with setup props but the same concept applies
Vue 3 watch doesnβt work if I watch a destructured prop
I would like to clear user data upon logout, so from auth module I dispatch an action which is defined in a module ride but I am getting an error:
[vuex] unknown local action type: clearUserData, global type: auth/clearUserData
This is my code:
store/modules/auth.js
export const namespaced = true
export const mutations = {
clearAuthData(state){
state.apiToken = null
state.signedIn = false
}
}
export const actions = {
logout({ commit, dispatch }) {
commit('clearAuthData'))
dispatch('ride/clearUserData', { root: true })
}
}
store/modules/ride.js
export const namespaced = true
export const state = {
data: []
}
export const mutations = {
CLEAR_USER_DATA(state){
state.data = []
}
}
export const actions = {
clearUserData({ commit }) {
commit('CLEAR_USER_DATA')
}
}
store/store.js
import * as auth from './modules/auth'
import * as ride from './modules/ride'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
loading: false
},
modules: {
auth,
ride
}
})
What am I doing wrong?
Your usage of dispatch() is incorrectly passing { root: true } as the 2nd argument, which is intended for the payload. Options should be 3rd:
// dispatch('ride/clearUserData', { root: true }) // FIXME: options passed as payload
dispatch('ride/clearUserData', null, { root: true })
If you don't have payload to pass, then you need to send the second parameter as a null like:
dispatch('ride/clearUserData', null, { root: true })