This question already has an answer here:
component data vs its props in vuejs
(1 answer)
Closed 1 year ago.
I have {{ story.score }} in a template and a function which updates the score:
setup(props) {
const changeScore = (value) => {
props.story.score = value;
}
return {
changeScore,
}
New value is assigned to props.story.score, but template doesn't rerender, what is missing here?
You can't change props directly in vue, it wont trigger an update. The proper way is to emit changes to parent element, like this:
setup({ props, emit }) {
const changeScore = (value) => emit('update-story-score', value);
return { changeScore }
}
Then just handle an event in parent, like this:
<template>
<Test :story="story" #update-story-score="updateStoryScore"></Test>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const story = reactive({ score: 1 });
const updateStoryScore = (value) => story.score = value;
return { story, updateStoryScore }
},
};
</script>
Vue props has a One-Way Data Flow
All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around
You need to emit an event. Check out v-model on components
Related
I have been troubled by a question for a long time. Now I am using Vue.js to develop a web project. What I want to do is to pass data from parent to child component. However, the child component's main program would run only after the props data was received, due to the async data transmission mechanism. So I would like to know whether these are some ways to check the status of props data in the child component. Therefore I can make sure the subsequent task would run after the data was passed.
For example, a feasible solution is axios.requset({..}).then(res => {..}).
You can use the watchers in your child component. Consider the following parent component:
Vue.component('parent-comp', {
props: ['myProp'],
template: `
<div>
<child-comp my-prop={someAsyncProp} />
</div>
`,
data() {
return {
// Declare async value
someAsyncProp: null
};
},
mounted() {
// Some async computation
axios
.requset({ url: '/get-data' })
.then(res => {
// Set value asynchronously
this.someAsyncProp = res;
});
}
});
Your child component would use watchers to check if data is available:
Vue.component('child-comp', {
props: ['myProp'],
template: '<div></div>',
watch: {
// Watch the property myProp
myProp(newValue, oldValue) {
if (newValue !== null) {
// Do something with the props that are set asynchronously by parent
}
}
}
})
For a form we have 2 components parent(for calling asyncdata and pass data as props to child) & child(form). I can properly fetch the props in child if I navigate using a link. But If I try to refresh the child component page it throws error as no props is passed. Found the reason to be that the parents asyncdata is not completing before the child render to sent the data in props.
Parent Component
<template>
<div>
<p>EDIT</p>
<NewListingModal :is-edit="true" :form-props="this.form" />
</div>
</template>
<script>
import NewListingModal from '#/components/NewListingModal.vue'
export default {
components: { NewListingModal },
async asyncData({ params, store }) {
const listing = await store.$db().model('listings').find(params.listing) //vuexorm call
if (typeof listing !== 'undefined') {
const convertedListing = JSON.parse(JSON.stringify(listing))
return {
name: '',
scrollable: true,
form: {names: convertedListing.names}
}
}
},
}
</script>
child component(other form data is removed to keep it understandable)
<template>
<div v-for="name in this.form.names" :key="name">
<p>{{ name }} <a #click.prevent="deleteName(name)">Delete<a /></a></p>
</div>
</template>
<script>
import Listing from '#/models/listing'
export default {
name: 'ListingModal',
props: {isEdit: {type: Boolean, default: false}, formProps: {type: Object}},
data() {
return {
name: '',
scrollable: true,
form: {names: this.formProps.names}
}
},
methods: {
addName() {
this.form.names.push(this.name)
this.name = ''
},
deleteName(name) {
const names = this.form.names
names.splice(names.indexOf(name), 1)
}
}
}
</script>
How can I make the NewListingModal component rendering wait until the asyncData completes in parent?
In my case, I used asyncData in my parent nuxt component, which fetches the data via store dispatch action, then set it to some store state key, via mutation.
Then I used validate method in my child component. Since Nuxt validate can return promises, I checked the vuex store first for fetched data. If there is none, I refetch it and return the promise instead.
In Parent component.vue
export default {
async asyncData({ params, store }) {
// Api operation which may take sometime
const {data} = await store.dispatch('fetch-my-data')
store.commit('setData', data) //This should be within above dispatch, but here for relevance
}
}
Here I am only fetching and saving to vuex store.
Child component.vue
export default {
async validate({ params, store }) {
let somedata = store.state.data //This is what you've set via parent's component mutation
return !!somedata || store.dispatch('fetch-my-data')
}
}
Here I am returning either the vuex store data (if exists), else refetch it.
I have the following WATCH property watching my v-model data (this is for checkboxes and I'm using bootstrap-vue).
I think my mistake is in how I'm setting the value in either the WATCH or how it is being called in my store? (is it an issue because an array is being sent over?)
I can console inside the WATCH, but when I dispatch, then I get the error.
Here is the checkbox markup:
<b-form-group label="Using sub-components:">
<b-form-checkbox-group id="checkboxes1" name="flavour1" v-model="yourAccounts">
<b-form-checkbox :value="test.value" class="card" v-for="test in filteredList" :key="bank.text">
{{ test.text }}
</b-form-checkbox>
</b-form-checkbox-group>
</b-form-group>
the computer property:
computed: {
yourAccountsState: {
get() {
// console.log(this.yourAccounts);
return this.yourAccounts
},
}
},
watch: {
yourAccountsState(value) {
this.$store.dispatch('setTestAccounts', value);
console.log(value);
}
}
In my store.js I have the following imported from a modules
const state = {
TestAccounts: []
}
const mutations = {
// from v-modal on selected accounts page
SET_SELECTED_TESTS (state, testAccount) {
state.TestAccounts = testAccount
}
}
const actions = {
setTestAccounts: ({commit}) => {
commit('SET_SELECTED_TESTS', value);
}
}
const getters = {
yourAccounts: state => {
return state.TestAccounts
}
}
export default {
state,
mutations,
actions,
getters
}
If you use v-model, you need to give Vue a setter, because v-model is a shorthand method which executes v-bind and v-on behind the scenes.
If you don't provide a setter, the watcher won't be triggered the way you want.
And you have to make sure, that the property you assign to v-model is an array, so that the values can be stored as described in the bootstrap-vue docs.
Question:
Is there any way to update the props of a manually mounted vue component/instance that is created like this? I'm passing in an object called item as the component's data prop.
let ComponentClass = Vue.extend(MyComponent);
let instance = new ComponentClass({
propsData: { data: item }
});
// mount it
instance.$mount();
Why
I have a non vue 3rd party library that renders content on a timeline (vis.js). Because the rest of my app is written in vue I'm attempting to use vue components for the content on the timeline itself.
I've managed to render components on the timeline by creating and mounting them manually in vis.js's template function like so.
template: function(item, element, data) {
// create a class from the component that was passed in with the item
let ComponentClass = Vue.extend(item.component);
// create a new vue instance from that component and pass the item in as a prop
let instance = new ComponentClass({
propsData: { data: item },
parent: vm
});
// mount it
instance.$mount();
return instance.$el;
}
item.component is a vue component that accepts a data prop.
I am able to create and mount the vue component this way, however when item changes I need to update the data prop on the component.
If you define an object outside of Vue and then use it in the data for a Vue instance, it will be made reactive. In the example below, I use dataObj that way. Although I follow the convention of using a data function, it returns a pre-defined object (and would work exactly the same way if I'd used data: dataObj).
After I mount the instance, I update dataObj.data, and you can see that the component updates to reflect the new value.
const ComponentClass = Vue.extend({
template: '<div>Hi {{data}}</div>'
});
const dataObj = {
data: 'something'
}
let instance = new ComponentClass({
data() {
return dataObj;
}
});
// mount it
instance.$mount();
document.getElementById('target').appendChild(instance.$el);
setTimeout(() => {
dataObj.data = 'another thing';
}, 1500);
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="target">
</div>
This has changed in Vue 3, but it's still possible when using the new Application API.
You can achieve this by passing a reactive object to App.provide():
<!-- Counter.vue -->
<script setup>
import { inject } from "vue";
const state = inject("state");
</script>
<template>
<div>Count: {{ state.count }}</div>
</template>
// script.js
import Counter from "./Counter.vue";
let counter;
let counterState;
function create() {
counterState = reactive({ count: 0 });
counter = createApp(Counter);
counter.provide("state", counterState);
counter.mount("#my-element");
}
function increment() {
// This will cause the component to update
counterState.count++;
}
In Vue 2.2+, you can use $props.
In fact, I have the exact same use case as yours, with a Vue project, vis-timeline, and items with manually mounted components.
In my case, assigning something to $props.data triggers watchers and the whole reactivity machine.
EDIT: And, as I should have noticed earlier, it is NOT what you should do. There is an error log in the console, telling that prop shouldn't be mutated directly like this. So, I'll try to find another solution.
Here's how I'm able to pass and update props programmatically:
const ComponentClass = Vue.extend({
template: '<div>Hi {{data}}</div>',
props: {
data: String
}
});
const propsObj = {
data: 'something'
}
const instance = new ComponentClass()
const props = Vue.observable({
...instance._props,
...propsObj
})
instance._props = props
// mount it
instance.$mount();
document.getElementById('target').appendChild(instance.$el);
setTimeout(() => {
props.data = 'another thing'; // or instance.data = ...
}, 1500);
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="target">
</div>
I am passing a variable from parent component to child component through props. But with some operation, the value of that variable is getting changed i.e. on click of some button in parent component but I did not know how to pass that updated value to child? suppose the value of one variable is false initially and there is Edit button in parent component. i am changing the value of this variable on click of Edit button and want to pass the updated value from parent to child component.
Your property's value should be updated dynamically when using props between parent and child components. Based on your example and the initial state of the property being false, it's possible that the value was not properly passed into the child component. Please confirm that your syntax is correct. You can check here for reference.
However, if you want to perform a set of actions anytime the property's value changes, then you can use a watcher.
EDIT:
Here's an example using both props and watchers:
HTML
<div id="app">
<child-component :title="name"></child-component>
</div>
JavaScript
Vue.component('child-component', {
props: ['title'],
watch: {
// This would be called anytime the value of title changes
title(newValue, oldValue) {
// you can do anything here with the new value or old/previous value
}
}
});
var app = new Vue({
el: '#app',
data: {
name: 'Bob'
},
created() {
// changing the value after a period of time would propagate to the child
setTimeout(() => { this.name = 'John' }, 2000);
},
watch: {
// You can also set up a watcher for name here if you like
name() { ... }
}
});
You can watch a (props) variable with the vue watch.
for example:
<script>
export default {
props: ['chatrooms', 'newmessage'],
watch : {
newmessage : function (value) {...}
},
created() {
...
}
}
</script>
I hope this will solve your problem. :)
Properties, where the value is an object, can be especially tricky. If you change an attribute in that object, the state is not changed. Thus, the child component doesn't get updated.
Check this example:
// ParentComponent.vue
<template>
<div>
<child-component :some-prop="anObject" />
<button type="button" #click="setObjectAttribute">Click me</button>
</div>
</template>
<script>
export default {
data() {
return {
anObject: {},
};
},
methods: {
setObjectAttribute() {
this.anObject.attribute = 'someValue';
},
},
};
</script>
// ChildComponent.vue
<template>
<div>
<strong>Attribute value is:</strong>
{{ someProp.attribute ? someProp.attribute : '(empty)' }}
</div>
</template>
<script>
export default {
props: [
'someProp',
],
};
</script>
When the user clicks on the "Click me" button, the local object is updated. However, since the object itself is the same -- only its attribute was changed -- a state change is not dispatched.
To fix that, the setObjectAttribute could be changed this way:
setObjectAttribute() {
// using ES6's spread operator
this.anObject = { ...this.anObject, attribute: 'someValue' };
// -- OR --
// using Object.assign
this.anObject = Object.assign({}, this.anObject, { attribute: 'someValue' });
}
By doing this, the anObject data attribute is receiving a new object reference. Then, the state is changed and the child component will receive that event.
You can use Dynamic Props.
This will pass data dynamically from the parent to the child component as you want.