I tried to sort the date using computed properties but it didnt work, if i using at methods and remove the slice, then it will sort as expected. I've tried to split the date also but still not working. I am not sure what caused the problem.
Kindly find my code as below.
App
<template>
<div>
<h2>Ingredients</h2>
<ul>
<li></li>
</ul>
<ingredients-list v-for="(ingredient,index) in ingredients"
:key="index"
:index='index'
:foodName="ingredient.food"
:foodExpiry="ingredient.expiryDate">
</ingredients-list>
</div>
</template>
<script>
export default {
data(){
return{
ingredients:[
{
food:'carrot',
expiryDate:'2020-12-12'
},
{
food:'papaya',
expiryDate:'2018-1-15'
},
{
food:'orange',
expiryDate:'2021-10-13'
},
{
food:'meat',
expiryDate:'2019-4-23'
}]
}
},
computed: {
sortedItems() {
return this.ingredients.slice().sort( ( a, b) => {
return new Date(a.expiryDate)- new Date(b.expiryDate);
});
}
}
}
</script>
components
<template>
<div>
<h2>{{index}}</h2>
<h2>Food:{{foodName}}</h2>
<h2>Expiry:{{foodExpiry}}</h2>
</div>
</template>
<script>
export default {
props:['foodName','foodExpiry'],
}
</script>
Like #Anatoly said
Computed props are never calculated if they are not used at all.
What you should be doing to solve the problem is :
Using slice method instead of splice since it mutates the original array:
sortedItems() {
return this.ingredients.slice()
.sort((a, b) => new Date(a.expiryDate)- new Date(b.expiryDate));
}
Loop through the computed property and not the original array:
<ingredients-list v-for="(ingredient,index) in sortedItems"
:key="index"
:index='index'
:foodName="ingredient.food"
:foodExpiry="ingredient.expiryDate">
</ingredients-list>
It seems you confused splice with slice. You need slice to get a copy of an array and to sort this copy:
sortedItems() {
return this.ingredients.slice().sort( ( a, b) => {
return new Date(a.expiryDate)- new Date(b.expiryDate);
});
}
I have an issue in the two way binding of a reactive component in vue 3 using the composition API.
The setup:
The parent calling code is:
<template>
<h1>{{ message.test }}</h1>
<Message v-model="message" />
</template>
<script>
import Message from '#/components/Message.vue';
import { reactive } from 'vue';
export default {
name: 'Home',
components: { Message },
setup() {
const message = reactive({ test: '123' });
return {
message
};
}
};
</script>
The child component code is:
<template>
<label>
<input v-model="message" type="text" />
</label>
</template>
<script>
import { computed } from 'vue';
export default {
props: {
messageObj: {
type: Object,
default: () => {},
},
},
emits: ['update:messageObj'],
setup(props, { emit }) {
const message = computed({
get: () => props.messageObj.test,
set: (value) => emit('update:messageObj', value),
});
return {
message,
};
},
};
</script>
The problem:
When the component is loaded, the default value from the object is shown in the input field.
This is as it should be, however, when I update the value in the input box the H1 in the parent view is not getting updated with the new input box value.
I have searched through the stackoverflow board and google but have not found any hint as to what needs to be done to make the object reactive.
I read through the reactivity documentation but still have not found any solution for my issue.
For testing I have changed message to be a ref and using this single ref value the data remains reactive and everything is working as expected.
Any pointers on what can be the issue with the reactive object not updating?
Here
<div id="app">
<h1>{{ message.test }}</h1>
<child v-model="message"></child>
</div>
const { createApp, reactive, computed } = Vue;
// -------------------------------------------------------------- child
const child = {
template: `<input v-model="message.test" type="text" />`,
props: {
modelValue: {
type: Object,
default: () => ({}),
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
return { message };
}
};
// ------------------------------------------------------------- parent
createApp({
components: { child },
setup() {
const message = reactive({ test: 'Karamazov' });
return { message };
}
}).mount('#app');
Solution and observations:
In the parent view which is calling the component you can use v-model and add a parameter to that v-model if you need to pass only one of the values in the object.
<template>
<h1>{{ message.test }}</h1>
<!-- <h1>{{ message }}</h1> -->
<Message v-model:test="message" />
</template>
<script>
import Message from '#/components/Message.vue';
import { reactive } from 'vue';
export default {
name: 'Home',
components: { Message },
setup() {
const message = reactive({ test: '123' });
return {
message
};
}
};
</script>
In the receiving component you then register the parameter of the object that was passed in props as an object.
<template>
<label>
<input v-model="message.test" type="text" />
</label>
</template>
<script>
import { computed } from 'vue';
export default {
props: {
test: {
type: Object,
default: () => {}
},
},
emits: ['update:test'],
setup(props, { emit }) {
const message = computed({
get: () => props.test,
set: (value) => emit('update:test', value),
});
return {
message,
};
},
};
</script>
If you need to pass the whole object you need to use as a prop in the component the name modelValue.
Change in parent compared to previous code:
<template>
<h1>{{ message.test }}</h1>
<!-- <h1>{{ message }}</h1> -->
<Message v-model="message" />
</template>
Code of the component:
<template>
<label>
<input v-model="message.test" type="text" />
</label>
</template>
<script>
import { computed } from 'vue';
export default {
props: {
modelValue: {
type: Object,
default: () => {}
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
return {
message,
};
},
};
</script>
Should be pretty straight forward, and no computed is needed. See example below.
The messageObj was replaced with message in the child component for the emit to work (which would break due to case sensitivity in this demo)
const app = Vue.createApp({
setup() {
const message = Vue.reactive({ test: '123' , foo: "bark"});
return {
message,
};
}
})
app.component('Message', {
props: {
message: {
type: Object,
default: () => {},
},
},
emits: ['update:message'],
setup(props, { emit }) {
const message = props.message;
return { message };
},
template: document.querySelector('#t_child')
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.2/dist/vue.global.prod.js"></script>
<fieldset>
<div id="app">
<h1>{{ message.test }} || {{ message.foo }}</h1>
<fieldset><Message v-model:message="message"/></fieldset>
</div>
</fieldset>
<template id="t_child">
<label>
<h4>{{message}}</h4>
<input v-model="message.test" type="text" />
<input v-model="message.foo" type="text" />
</label>
</template>
Your initial problem is quite simple. In Vue 3 v-model defaults to to a prop called modelValue and emits come from update:modelValue. Other answers here have assumed that in their solutions but not directly addressed it.
You can either rename your messageObj prop to use the default prop OR use the multi-model features in Vue 3:
<Message v-model:messageObj="message" />
However our problems run deeper.
All (current) answers will work but aren't quite correct. They all fail the idiomatic "One-way Data Flow" rule.
Consider this JSFiddle, modified from this answer.
const child = {
template: `<input v-model="message.test" type="text" />`,
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue,
// No set() ?
});
return { message };
}
}
In this example, the child component never 'emits' - yet the data is still updating in the parent component. This violates the "One-way" rule. Data must be propagated from child components using only emits and not via prop proxies.
The problem in here is that props.modelValue is reactive when arrives in the child component. One can verify this with the isReactive() helper. When it's passed through the computed() it retains that reactiveness and will continue to proxy updates through itself into the parent component.
A solution:
JSFiddle here
const { createApp, ref, computed } = Vue;
const child = {
template: `<input v-model="message" type="text" />`,
props: {
modelValue: {
type: Object,
default: () => ({}),
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const message = computed({
get: () => props.modelValue.test,
set: (test) => emit('update:modelValue', ({...props.modelValue, test })),
});
return { message };
}
};
createApp({
components: { child },
setup() {
const message = ref({ test: 'Karamazov' });
return { message };
}
}).mount('#app');
The solution is three parts:
The computed getter must not return the proxy object from the parent component. Once this happens you're in danger of violating the "one-way" rule [note 1]. In this example props.modelValue.test is a string so we're safe.
The computed setter must emit the whole object, but again it must not be a reactive type. So we clone the modelValue using spread and include in the updated test field. This can also be achieved with Object.assign({}, props.modelValue, {test}) [note 2].
The message variable in the parent component cannot be a reactive() and must be a ref(). When the v-model receives the newly emitted object the message variable is clobbered and no longer reactive [note 3]. Even with refs the props.modelValue will still fully reactive when it arrives in the child component, so the cloning steps are still important.
Alternatively:
I should also mention that values from computed() are not deeply reactive. As in, setting values on a computed object will not trigger the computed setter.
An alternate solution for passing the whole object through to your template:
setup(props, { emit }) {
const message = reactive({...props.modelValue});
watch(message, message => emit('update:modelValue', ({...message})));
return { message };
}
In this, the whole message object will emit whenever the .test field is updated. E.g. <input v-model="message.test" />. This still obeys the "one-way" data rule because emits are the only way data is given to parent component.
Reasoning:
"One-way" data flow is important [4]. Consider this:
<child :modelValue="message"></child>
On a first (and a sensible) glance, this appears to pass data into 'child' but not out of 'child'. But, given a reactive object that is not handled by the child correctly, this will emit changes into my own component.
Observing this code I don't expect this behaviour so it's very important that the child component gets it right.
Notes:
[1]: Testing violations of the "one-way" rule are surprisingly simple. Remove any emit and if the parent receives updates - you've broken it. Or replacing v-model with v-bind also works.
[2]: Object.assign() and {...} spread are indeed different. But shouldn't affect our uses here.
[3]: I haven't found any clear documentation about this behaviour regarding reactive() and v-model. If anyone wants to chime in, that'd be great.
[4]: The Vue docs stress the importance of one-way bind. Evan himself (creator of Vue) even provides examples about how to use v-model with objects (in Vue 2, but the principles still apply).
I feel it's also important to note later in the same thread Evan suggests objects that are nested more than 1-level are considered misuse of v-model.
It turns out that 2 way binding of object properties with Vue 3 is even easier than demonstrated in any of the previous answers.
Parent Code (App.vue):
<script setup>
import Controller from './components/Controller.vue';
import { reactive } from 'vue';
const object1 = reactive({name: "Bruce", age: 38});
const object2 = reactive({name: "Alex", age: 6});
</script>
<template>
<div>
{{object1}}<br/>
{{object2}}
<Controller :my-object="object1"/>
<Controller :my-object="object2"/>
</div>
</template>
Component code (Controller.vue):
<script setup>
import { computed } from 'vue'
const props = defineProps({
myObject: {
type: Object,
default: () => {}
}
})
const name = computed({
get () {
return props.myObject.name
},
set (value) {
props.myObject.name = value
}
})
const age = computed({
get () {
return props.myObject.age
},
set (value) {
props.myObject.age = parseInt(value)
}
})
</script>
<template>
<div>
<input v-model="name"/><br/>
<input v-model="age" type="number"/>
</div>
</template>
Explanation:
The <component :my-object="object1" /> syntax uses a : to tell Vue that we are passing an object (object1), rather than a string to the component and assigning it to property myObject. It turns out that when the child component receives this property, its reactivity is still intact. Therefore, as long as we don't mutate myObject itself, but instead only modify its properties, there is no need to emit any events or even pass it with as a property called v-model (we can call the property whatever we want). Instead the javascript proxy that the reactive keyword creates will do all the work tracking the changes and re-rendering it.
Some testing reveals that it is even possible to add new properties to the object or change deep properties and still maintain reactivity.
I am just a beginner with Vue, so there may be reasons why using this method are an anti-pattern, with unintended future consequences...
So I pass value using [props] and stored it in child component's data. However, when passing [props] value changes from parent, it's not updating in child component's data. Is there a fix for this..?
Here is the link to w3 test (I tried to clarify the problem as much as possible here)
<div id='app'>
<div id='parent'>
<button #click='current_value()'>Click to see parent value</button>
<br><br>
<button #click='change_value($event)'>{{ txt }}</button>
<br><br>
<child-comp :test-prop='passing_data'></child-comp>
</div>
<br><br>
<center><code>As you can see, this methods is <b>NOT</b> reactive!</code></center>
</div>
<script>
new Vue({
el: "#parent",
data: {
passing_data: 'Value',
txt: 'Click to change value'
},
methods: {
current_value(){
alert(this.passing_data);
},
change_value(e){
this.passing_data = 'New Vaule!!';
this.txt = 'Now click above button again to see new value';
e.target.style.backgroundColor = 'red';
e.target.style.color = 'white';
}
},
components: {
"child-comp": {
template: `
<button #click='test()'>Click here to see child (stored) value</button>
`,
props: ['test-prop'],
data(){
return {
stored_data: this.testProp
}
},
methods: {
test(){
alert(this.stored_data);
}
},
watch: {
stored_data(){
this.stored_data = this.testProp;
}
}
}
}
});
Props have one way data flow, that's why it doesn't react when you update it from the parent component. Define a clone of your prop at data to make it reactive, and then you can change the value within the child component.
Short answer: you don't need stored_data. Use alert(this.testProp) directly.
Long answer: when child component is created, stored_data get it's value from this.testProp. But data is local, it won't change automatically. That's why you need to watch testProp and set it again. But is not working because of a simple mistake, your watch should be:
watch: {
testProp(){ // here was the mistake
this.stored_data = this.testProp;
}
}
I have the following components:
/components/SearchBlogs.vue Search component to filter on blog.title and blog.description.
/components/BlogList.vue Here I list all the Blog items.
SearchBlogs.vue
<template>
<div>
<input type="text" v-model="search" #change="emitSearchValue" placeholder="search blog">
</div>
</template>
<script>
import { EventBus } from '../event-bus.js'
export default {
name: 'SearchBlogs',
data: () => {
return {
search: ''
}
},
methods: {
emitSearchValue() {
EventBus.$emit('search-value', 'this.search')
}
}
}
</script>
BlogList.vue
<template>
<div>
<div v-for="blog in filteredBlogs" :key="blog">
<BlogListItem :blog="blog" />
</div>
</div>
</template>
<script>
import BlogListItem from './BlogListItem'
import { EventBus } from '../event-bus.js'
export default {
name: 'BlogList',
components: {
BlogListItem,
},
data: () => {
return {
blogs: [],
searchvalue: ''
}
},
computed: {
filteredBlogs() {
return this.blogs.filter(blog =>
blog.name.toLowerCase().includes(
this.searchvalue.toLowerCase()
)
)
}
},
created() {
fetch('http://localhost:3000/blogs')
.then(response => {
return response.json();
})
.then(data => {
this.blogs = data;
}),
EventBus.$on('search-value', (search) => {
this.searchvalue = value;
})
}
}
</script>
In another page component Blogs I register both components:
<template>
<div>
<h1>Blog</h1>
<TheSidebar>
<SearchBlogs />
</TheSidebar>
<BlogList/>
</div>
</template>
Can anybody see what's missing here? I want, as soon as the user types something in the search input (from the SearchBlogs.vue component), it start filtering and updating the list.
Look at my solution condesandbox
Here is an explanation:
You don't need to use EventBus. You can communicate with Search Component by v-model, using prop value and emiting updated value from the Input.
Then your Main (List) Component is responsible for all the logic.
It keeps the state of a Search
It keeps the items and filtered Items
Thanks to that your Search Component is very clear and has no data, that means it has very little responsibility.
Please ask questions if I can add something to help you understand 😉
UPDATE:
EventBus is a great addition in some cases. Your case is simple enough, there is no need to add it. Right now your architecture is "over engineered".
When you have added listener on EventBus, on created:hookyou should always remove it while Component is being destroyed. Otherwise you can encounter a trouble with double calling function etc. This is very hard to debug, tryst me I'he been there 😉
Going with my suggestion gives you comfort of "no-need-to-remember-about-this" because Vue is doing it for you.
Hope that help.
Couple of issues but essentially the computed prop filteredData will look like:
computed: {
filteredData() {
return this.experiences.filter(
el => el.category.indexOf(this.search) > -1
);
}
}
Also, used quotes around 'this.search' when passing its value back which made it a string.
Fixed sandbox
https://codesandbox.io/s/reverent-lamarr-is8jz
This is a simple component. I'm trying to assign props to data as docs said so. (the initialData comes from vuex and database)
<template>
<section>
{{ initialData }}
{{ privateData }}
</section>
</template>
<script>
export default {
name: 'someName',
props: [
'initialData'
],
data() {
return {
privateData: this.initialData
};
}
};
But, the problem is initialData is OK, but privateData is just an empty object {}.
Weirdest thing is, if I save my file again, so webpack hot reloads stuff, privateData also gets the proper data I need.
Here is the parent:
<template>
<section v-if="initialData">
<child :initial-data="initialData"></micro-movies>
</section>
</template>
<script>
export default {
name: 'parentName',
data() {
return {};
},
computed: {
initialData() {
return this.$store.state.initialData;
}
},
components: {
child
}
};
</script>
I know that it's about getting data dynamically . because if I change initialData in parent to some object manually, it works fine.
The data function is only ever called once at component creation. If initialData is not populated at that point in time, then privateData will always be null. That is why you probably want to use a computed property, or watch the property.