Vue same component inputs doesnt update - javascript

I have reused same component at navigation menu and at peoples page
My issue is that when SearchComponent inputs in navigation updates its value Peoples page SearchComponent inputs doesnt update it's value vice versa. How should I do/achieve this in Vue JS. Below are my codes
SearchComponent
<template>
<div class="search">
<input type="text" v-model="name">
<input type="text" v-model="email">
<input type="text" v-model="age">
</div>
</template>
<script>
export default {
name: 'SearchComponent',
data() {
return {
name: '',
email: '',
age: ''
}
}
}
</script>
Navigation
<template>
<div class="nav">
<ul>
<li>Home</li>
<li>Contact</li>
<li>About</li>
</ul>
<SearchComponent />
</div>
</template>
<script>
import SearchComponent from './SearchComponent'
export default {
name: 'NavComponent',
data() {
return {
name: '',
email: '',
age: ''
}
},
components: {SearchComponent}
}
</script>
Peoples Page
<template>
<div class="persons">
<SearchComponent />
<PersonList />
</div>
</template>
<script>
import SearchComponent from './SearchComponent'
import PersonList from './PersonList'
export default {
name: 'PersonsPage',
data() {
return {
name: '',
email: '',
age: ''
}
},
components: {SearchComponent, PersonList}
}
</script>

Adding to #Eli, Try this way to share the same object reference for all the instances' data:
<template>
<div class="persons">
<SearchComponent />
<PersonList />
</div>
</template>
<script>
import SearchComponent from './SearchComponent'
import PersonList from './PersonList'
const personPageData = {
name: '',
email: '',
age: ''
};
export default {
name: 'PersonsPage',
data() {
return personPageData; //Same state object referred by all instances.
},
components: {SearchComponent, PersonList}
}
</script>

That’s because each time you use a Vue Component, a new instance of it is created
Since components are reusable Vue instances, they accept the same options as new Vue, such as data, computed, watch, methods, and lifecycle hooks. The only exceptions are a few root-specific options like el.
So if you want if you want all Instance to be affected as single component Instead, a component’s data should not be declared as function but as object instead i.e.
data: {
name: '',
email: '',
age: ''
}
Instead of
data: function () {
return {
name: '',
email: '',
age: ''
}
}
For which in your case you have defined it as:
data() {
return {
name: '',
email: '',
age: ''
}
}
You can also find more about Component Reusing and also Organizing Components and Since SearchComponent and PersonList component are child of Peoples Component you also need to have idea how to pass data from Parent Component i.e Peoples to Child components

Those SearchComponent even if they are the same component, they are different instances. Each one of them have their own independent data. If you want both to be in sync I'd recommend to use Vuex to have a global store that they can use as a source of truth.

Related

VueJS: Pass any unknown props to child component just like v-bind="$props"

I want to receive any props bind by the parent component into the child component without mentioning in props:[] because I don't know which props will bind.
Parent component
<template>
<div id="parentComponent">
<child-component v-bind="anyPropsToPass"></child-component>
</div>
</template>
<script>
import ChildComponent from './components/child-component/child-component'
export default {
name: 'app',
components: {
ChildComponent
},
data () {
return {
anyPropsToPass: {
name: 'John',
last_name: 'Doe',
age: '29',
}
}
}
}
</script>
Child component
<template>
<div>
<p>I am {{name}} {{last_name}} and i am {{age}} old</p>
<another-child v-bind="$props"></another-child> <!-- another child here and we pass all props -->
</div>
</template>
<script>
import AnotherChild from "../another-child/another-child";
export default {
components: {AnotherChild},
props: [], // I know if I mentioned props here I can receive but it's unknown, I
//just want to pass it down until it received in right component to use
created() {
console.log("Props", this.$props);
// Gets null
// Expected : anyPropsToPass Object
}
}
</script>
If props are mentioned in the props of child then it works but there should be some way to know which are the props passed or bind from the parent even though we are not interested in child.
e.g. Working fine!
Child component
<template>
<div>
<p>I am {{name}} {{last_name}} and i am {{age}} old</p>
<another-child v-bind="$props"></another-child>
</div>
</template>
<script>
import AnotherChild from "../another-child/another-child";
export default {
components: {AnotherChild},
props: ['name', 'last_name'],
created() {
console.log("Props", this.$props);
// Gets expected props here
}
}
</script>
You can use this.$attrs to get all v-bind props, including undeclared props.
<!-- parent -->
<template>
<div id="app">
<ChildComp :prop1="114" prop2="514" class="homo" />
</div>
</template>
<script>
// child component
export default {
created() {
console.log('all props: ', this.$attrs) // { "prop1": 114, "prop2": "514" }
}
}
</script>
This should be possible with this.$attrs
Child.vue:
<template>
<div>
<p>I am {{getValue('name')}} {{getValue('last_name')}} and i am {{getValue('age')}} old</p>
</div>
</template>
<script>
export default {
methods:{
getValue(propertyName){
return this.$attrs[propertyName];
}
},
components: {},
props: [],
created() {
console.log("Props", this.$attrs);
}
}
</script>
Drilling down the props is a bad pattern, which can lead to inconsistency, try to use the provide/inject pattern to pass data from grandparent component to the grandchild one :
<template>
<div id="parentComponent">
<child-component></child-component>
</div>
</template>
<script>
import ChildComponent from './components/child-component/child-component'
export default {
name: 'app',
components: {
ChildComponent
},
provide () {
return {
user: this.user
}
},
data () {
return {
user: {
name: 'John',
last_name: 'Doe',
age: '29',
}
}
}
}
</script>
in grandchild component :
<template>
<div>
<p>I am {{user.name}} {{user.last_name}} and i am {{user.age}} old</p>
<another-child></another-child>
</div>
</template>
<script>
import AnotherChild from "../another-child/another-child";
export default {
components: {AnotherChild},
inject:['user'],
created() {
console.log("injected user", this.user);
}
}
</script

Vue 3: Unable to update parent data from child component checkbox

I'm trying to move a checkbox to a child component, but I can't get it to update the data stored in the parent.
Parent:
<template>
<CheckboxComponent
:value="profile.doYouAgree"
label="Do you agree?"
#input="profile.doYouAgree = $event.target.value"
/>
<div>{{ profile.doYouAgree }}</div>
</template>
<script>
import CheckboxComponent from "./components/CheckboxComponent.vue";
import { reactive } from "vue";
const profile = reactive({
name: "A Name",
email: "someone#me.com",
doYouAgree: false,
});
export default {
name: "App",
components: {
CheckboxComponent,
},
setup() {
return {
profile,
};
},
};
</script>
Child:
<template>
<div class="hello">
<label for="checkbox">{{ label }}</label>
<input
type="checkbox"
:value="modelValue"
right
#input="$emit('update:modelValue', $event.target.value)"
/>
</div>
</template>
<script>
export default {
name: "CheckboxComponent",
props: {
label: {
type: String,
default: "",
},
modelValue: {
type: Boolean,
},
},
};
</script>
In the following sandbox, I am expecting the false to turn to true when the box is checked:
https://codesandbox.io/s/gifted-worker-vm9lyt?file=/src/App.vue
There's a couple of problems here:
$event.target.value is a string rather than a boolean. Change this to $event.target.checked
Your parent is listening to input but your child is emitting update:modelValue. Save yourself a lot of hassle and use v-model in the parent:
<CheckboxComponent
v-model="profile.doYouAgree"
label="Do you agree?"
/>

Avoid mutating props in nested forms

Let's say I have a form divided in two sections each communicated with parent form via props. How do I avoid mutating props passed from parent form?
ParentForm.vue
<template>
<div>
<AddressForm :form-data="formData.address" />
<OtherForm :form-data="formData.other" />
</div>
</template>
<script>
export default {
data() {
return {
formData: {
address: {
address_no: "",
country: "",
},
other: {
remarks: "",
},
},
};
},
};
</script>
AddressForm.vue
<template>
<div>
<input v-model="formData.address_no" />
<input v-model="formData.country" />
</div>
</template>
<script>
export default {
name: "AddressForm",
props: { formData: { type: Object, required: true } },
};
</script>
OtherForm.vue
<template>
<div>
<textarea v-model="formData.remarks" />
</div>
</template>
<script>
export default {
name: "OtherForm",
props: { formData: { type: Object, required: true } },
};
</script>
Changing value of input fields in AddressForm.vue or OtherForm.vue will trigger a warning on props mutation. I wanted to know how do I communicate up/down with the forms without mutating a props?
I have come up with possible solutions but I don't know which one should I go for (and also how to implement it)
Clone formData prop in created() and link v-model to the local cloned formData instead of prop. Then watch the cloned formData and emit every time the value changed.
Bind using v-model instead of props
Make your child forms as custom inputs using v-model instead of passing props:
AddressForm.vue
<template>
<div>
<input :value="value.address_no" #input="$emit('input',{...value,address_no:$event.target.value)" />
<input :value="value.country" #input="$emit('input',{...value,country:$event.target.value)" />
</div>
</template>
<script>
export default {
name: "AddressForm",
props: { value: { type: Object, required: true } },
};
</script>
OtherForm :
<template>
<div>
<textarea :value="value.remarks" #input="$emit('input',{...value,remarks:$event.target.value)"/>
</div>
</template>
<script>
export default {
name: "OtherForm",
props: { value: { type: Object, required: true } },
};
</script>
in parent component :
<AddressForm v-model="formData.address" />
<OtherForm v-model="formData.other" />
You could assign in beforeMount() those props to some data values and then $emit them back.
Obviously, v-model should refer to the properties defined in data().

How to pass props to component in slot?

I am developing a Vue app with pimcore and twig in the backend. I have to create a component that receives the slot (another component), and render it inside, but with dynamic props.
Here is root in viani.twig.html:
<div>
<viani-accordion>
<viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox>
</viani-accordion>
</div>
There is nothing special. viani-accordion is a parent component and the viani-filter-checkbox is a slot, which I have to render with appropriate props.
Here you can see the VianiAccordion.vue:
<template>
<div class="wrapper">
<AccordionTitle v-for="(item, index) in dataToRender" :item="item" :key="index">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filter-rows="item.items"></slot>
</AccordionTitle>
</div>
</template>
<script>
import AccordionTitle from './Accordion-Title';
export default {
name: "Viani-Accordion",
components: {AccordionTitle},
data() {
return {
dataToRender: [
{
name: 'first item',
items: [
{
name: 'oil olive',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
},
{
name: 'second item',
items: [
{
name: 'subitem 1',
quantity: 10,
},
{
name: 'subitem 2',
quantity: 11,
},
]
}
]
}
},
}
</script>
Then I have another deeper child component Accordion-Title.vue that is responsible for rendering the slot (so I have to pass the slot through the multiple components):
<template>
<div v-if="isOpen" class="child-wrapper">
/*I think this is incorrect, but I'm trying to prop data that I need in viani-filter-checkbox*/
<slot :filterRows="item.items"></slot>
</div>
</template>
<script>
export default {
name: "Accordion-Title",
props: {
item: {
type: Object,
default: null
}
}
}
</script>
and finally Viani-FiltersCheckbox.vue:
<template>
<div>
//child component which we don't need in this case
<FilterCheckboxRow v-for="(item, index) in filterRows" :item="item" :key="index"/>
</div>
</template>
<script>
import FilterCheckboxRow from './FilterCheckboxRow'
export default {
name: "VianiFilterCheckbox",
components: {
FilterCheckboxRow
},
props: {
//here I expect to get array to render, but got default value (empty array)
filterRows: {
type: Array,
default: function () {
return []
}
},
},
}
</script>
So I need to pass the props (filterRows) to the component (Viani-FiltersCheckbox.vue), which is rendered as a slot. I have read this and this, but still don't get where the mistake and how to get the props I need.
It looks like you're trying to access your props through props.XXX. That's typically only done in templates for functional components. Otherwise, the props would be accessed without the props. prefix (i.e., props.item.items should be item.items).
And to pass filterRows from the scope data to the child component, declare a <template>, and then move your child into that, binding filterRows there:
<viani-accordion>
<!-- BEFORE: -->
<!-- <viani-filter-checkbox v-slot="{filterRows}"></viani-filter-checkbox> -->
<template v-slot="{filterRows}">
<viani-filter-checkbox :filterRows="filterRows"></viani-filter-checkbox>
</template>
</viani-accordion>

nuxt.js passing prop to component's inner element

I have a simple component:
<template>
<div id="search__index_search-form">
<input :foo-id="fooId" #keyup.enter="findFoos()" type="text" :value="keyword" #input="updateKeyword"
placeholder="Search for a foo">
<button #click="findFoos()">Search!</button>
{{fooId}}
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
computed: mapState({
keyword: state => state.search.keyword
}),
data: function () {
return {fooId: "all"};
},
methods: {
updateKeyword(e) {
this.$store.commit("setSearchKeyword", e.target.value);
},
findFoos() {
this.$store.dispatch("findFoos");
}
}
};
</script>
I am calling it from nuxt page:
<template>
<searchbar v-bind:fooId="500"/>
</template>
<script>
import searchBar from "~/components/search-bar.vue";
export default {
components: {
'searchbar': searchBar
}
};
</script>
This results in:
<div id="search__index_search-form" fooid="500"><input shop-id="all" type="text" placeholder="Search for a foo"><button>Search!</button>
all
</div>
Question is, why is fooId bound to "div.search__index_search-form" and not to input? And how come {{fooId}} results in "all" (default state), and not "500"?
fooId is rendered on div#search__index_search-form because you do not declare fooId as a property of the component. Vue's default behavior is to render undeclared properties on the root element of the component.
You need to declare fooId as a property.
export default {
props: {
fooId: {type: String, default: "all"}
},
computed: mapState({
keyword: state => state.search.keyword
}),
methods: {
updateKeyword(e) {
this.$store.commit("setSearchKeyword", e.target.value);
},
findProducts() {
this.$store.dispatch("findFoos");
}
}
};
I'm not sure what you are really trying to accomplish though.
<input :foo-id="fooId" ... >
That bit of code doesn't seem to make any sense.

Categories