export default {
setup() {
const imageUrl = ref("");
const title = ref("");
const description = ref("");
var source = "Global";
const form = reactive({
title: "",
description: "",
// src: source,
});
//...
}
}
How do I add src: "source" (a new property) to form (the reactive object)? Like how we append on ref.
You can assign the field directly via form.src = source. For example, you could have a function that does this:
const addSourceToForm = () => form.src = source
And you can call that function later, e.g., from a button click:
<button #click="addSourceToForm">Add source</button>
demo
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 pass data into a component via props:
<Comment :comment="currentCase.Comment" #comment="(c) => currentCase.Comment=c"></Comment>
currentCase is updated via a fetch call to an API during the setup of the component (the one that contains the line above)
The TS part of <Comment> is:
<script lang="ts" setup>
import { Comment } from 'components/helpers'
import { ref, watch } from 'vue'
const props = defineProps<{comment: Comment}>()
const emit = defineEmits(['comment'])
console.log(props)
const dateLastUpdated = ref<string>(props.comment?.DateLastUpdated as string)
const content = ref<string>(props.comment?.Content as string)
watch(content, () => emit('comment', {DateLastUpdated: dateLastUpdated, Content: content}))
</script>
... where Comment is defined in 'components/helpers' as
export class Comment {
DateLastUpdated?: string
Content?: string
public constructor(init?: Partial<Case>) {
Object.assign(this, init)
}
}
content is used in the template, but is empty when the component is rendered. I added a console.log() to check whether the props were known - and what is passed is undefined at that point:
โธ Proxyย {comment: undefined}
When looking at the value of the props once the application is rendered, their content is correct:
{
"comment": {
"DateLastUpdated": "",
"Content": "comment 2 here"
}
}
My question: why is comment not updated when props are available (and when are their content available?)
I also tried to push the update later in the reactive cycle, but the result is the same:
const dateLastUpdated = ref<string>('')
const content = ref<string>('')
onMounted(() => {
console.log(props)
dateLastUpdated.value = props.comment?.DateLastUpdated as string
content.value = props.comment?.Content as string
watch(content, () => emit('comment', {DateLastUpdated: dateLastUpdated, Content: content}))
})
Vue lifecycle creates component instances from parent to child, then mounts them in the opposite order. Prop value is expected to be available in a child if it's available at this time in a parent. If currentCase is set asynchronously in a parent, the value it's set to isn't available on component creation, it's a mistake to access it early.
This disables the reactivity:
content.value = props.comment?.Content as string
props.comment?.Content === undefined at the time when this code is evaluated, it's the same as writing:
content.value = undefined;
Even if it weren't undefined, content wouldn't react to comment changes any way, unless props.comment is explicitly watched.
If content is supposed to always react to props.comment changes, it should be computed ref instead:
const content = computed(() => props.comment?.Content as string);
Otherwise it should be a ref and a watcher:
const content = ref();
const unwatch = watchEffect(() => {
if (props.comment?.Content) {
content.value = props.comment.Content;
unwatch();
...
}
});
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);
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'm having a Vuex getters issue where the gitters return as undefined (in the Vue Dev Console and no errors are logged in the Chrome Dev Console).
If mapGetters() is commented out (like the example code below), the returned data is displayed on screen -> Providing if the user clicks into the link that has the data. The data will NOT display if the user enters the app directly at the point where the data should display.
There is a similar question but there is no accepted answer
Vue Console Logs:
STATE:
$_lgdHRS:Object
totHrs:129
GETTERS:
$_lgdHRS/totHrs:undefined
SomeContainer.vue
<script>
import store from '../../_store'
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState('$_lgdHRS',{
totHrs : 'totHrs',
}),
// ...mapGetters('$_lgdHRS',{
// totHrs : 'totHrs',
// airHrs : 'airHrs',
// picHrs : 'picHrs',
// pmcHrs : 'pmcHrs',
// voHrs : 'voHrs',
// trngHrs : 'trngHrs'
// }),
},
created() {
this.storeKey = '$_lgdHRS';
if (!(this.storeKey in this.$store._modules.root._children)) {
this.$store.registerModule(this.storeKey, store);
}
},
mounted() {
this.$store.dispatch('$_lgdHRS/getLogSummary');
},
}
</script>
<template>
<total-summary :hours="totHrs" />
</template>
state.js
export const state = {
totHrs: Number,
}
getters.js
const totHrs = state => state.totHrs;
export default {
totHrs,
};
mutations.js
const
TOTAL_HRS_UPDATED = (state, totHrs) => {
state.totHrs = +totHrs;
};
export default {
TOTAL_HRS_UPDATED,
};
Most probably because you have just displatched the request in mounted and before the data is set into the state variable your component is displayed.
Hence you can trying using async await in mounted as well as in store actions.
Do refer the following link and check the last example in this.
https://vuex.vuejs.org/guide/actions.html
The problem was that I was nesting my variables as I usually would in other frameworks.
Example:
// NESTED VARS
const r = response
totHrs = r.total,
airHrs = r.airborne,
picHrs = r.PIC,
pmcHrs = r.PMC,
voHrs = r.VO,
trngHrs = r.training;
// CHANGE TO:
const r = response
const totHrs = r.total
const airHrs = r.airborne
const picHrs = r.PIC
const pmcHrs = r.PMC
const voHrs = r.VO
const trngHrs = r.training
I don't know enough to why but your input would be greatly appreciated in the comments.