Vuejs moving data between multiple components - javascript

I have a parent component with two children components that I need data to move between. I can get emitted data from one child (multiselect.vue) to the parent, but cannot get it to then move into the other child component (render.vue). I suspect it has something to with v-model. Below is a simplifed version.
Parent component
<template>
<div>
<multiSelect v-model="content" />
<renderSplashPage :content="render" />
</div>
</template>
<script>
import multiSelect from '#/utils/multiSelect'
import render from '#/components/Render'
export default {
components: {
multiSelect,
render,
},
name: 'somename',
data(){
return{
content: {},
render: {}
}
},
watch: {
'content': function(val) {
this.render = val;
},
},
}
</script>
How can I get the emitted data to be 'watched' and 'picked up' by the 'render' component?

render is a reserved name (i.e., the internal render function cannot be overwritten), and for some reason, Vue silently ignores attempts to modify it.
Rename your property (e.g., to "myRender"), and you should see reactivity. If myRender is always equal to content, you could actually just replace it with content in your binding:
<multiselect v-model="content" :options="options" />
<render :content="content" />
demo

I used a variation of this here to solve my issue:
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
Taken from here:
https://v2.vuejs.org/v2/guide/reactivity.html

Related

Pass data from parent component to child of child in Vue js

I am deperately searching for a way to pass data from a parent component to a child of the child component.
I know I can use props to do this like in this thread Vue JS Pass Data From Parent To Child Of Child Of Child
But I only get it to work for the "next" child component and I get stuck for the second one.
My parent component:
<ResultBar :carbondata="realCarbonValues" />
data() {
return {
realCarbonValues: {
slabs: 20,
beams: 0,
columns: 0,
foundation: 0,
core: 0
}
};
My child component:
props: {
carbondata:Object
},
console.log(this.carbondata.slabs)
Now, I can log the data in the console from by using the child component
But what do I have to do in the next child componet to pass the data correctly?
depending on what you are trying to do you can just pass it directly as prop again.
Parent:
<Child :prop-i-wanna-pass-deeply="something" />
Child:
<GrandChild :props-i-wanna-pass-deeply-again="propsIWannaPassDeeply" />
GrandChild:
<div>{{ propsIWannaPassDeeplyAgain }} is now here</div>
or just use provide and inject to avoid prop-drilling.
See: https://v2.vuejs.org/v2/api/#provide-inject
Read the note about reactivity of provide/inject.
Parent:
<Child /> <!-- no need to pass the prop -->
provide() {
return {
something: "fooAbc",
}
}
Child:
<GrandChild />
GrandChild:
<div>{{ something }}</div>
inject: ['something']

Trying to avoid mutating a prop that goes 3 levels deep by using $emit

EDIT: Here's a repo I made for easier parsing.
I have a Component that lists products in a datatable. The first column of the table is a link that shows a modal with a form of the product that was clicked (using its ID). I'm using the PrimeVue library for styling and components.
<template>
<Column field="id" headerStyle="width: 5%">
<template #body="slotProps">
<ProductForm :product="slotProps.data" :show="showModal(slotProps.data.id)" />
<a href="#" #click.stop="toggleModal(slotProps.data.id)">
<span class="pi pi-external-link"> </span>
</a>
</template>
</Column>
</template>
<script>
import ProductForm from "./forms/ProductForm";
export default {
data() {
return {
activeModal: 0,
}
},
components: { ProductForm },
methods: {
toggleModal: function (id) {
if (this.activeModal !== 0) {
this.activeModal = 0;
return false;
}
this.activeModal = id;
},
showModal: function (id) {
return this.activeModal === id;
},
},
</script>
The modal is actually a sub component of the ProductForm component (I made a template of the Modal so I could reuse it). So it's 3 components all together (ProductList -> ProductForm -> BaseModal). Here's the product form:
<template>
<div>
<BaseModal :show="show" :header="product.name">
<span class="p-float-label">
<InputText id="name" type="text" :value="product.name" />
<label for="name">Product</label>
</span>
</BaseModal>
</div>
</template>
<script>
import BaseModal from "../_modals/BaseModal";
export default {
props: ["product", "show"],
components: { BaseModal },
data() {
return {};
},
};
</script>
When the modal pops up it uses the ProductForm subcomponent. Here is the BaseModal component:
<template>
<div>
<Dialog :header="header" :visible.sync="show" :modal="true" :closable="true" #hide="doit">
<slot />
</Dialog>
</div>
</template>
<script>
export default {
props: {
show: Boolean,
header: String,
},
methods: {
doit: function () {
let currentShow = this.show;
this.$emit("showModel", currentShow)
},
},
data() {
return {
};
},
};
</script>
I'm passing the product object, and a show boolean that designates if the modal is visible or not from the first component (ProductList) all the way down through the ProductForm component and finally to the BaseModal component. The modal is a PrimeVue component called Dialog. The component actually has it's own property called "closable" which closes the modal with an X button when clicked, that is tied to an event called hide. Everything actually works. I can open the modal and close it. For some reason I have to click the another modal link twice before it opens after the initial.
The issue is when I close a modal, I get the Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "show" error. I've tried everything to emit to the event and change the original props value there, but the error persists (even from the code above) but I'm not sure if because I'm 3 components deep it won't work. I'm pretty new to using props and slots and $emit so I know I'm doing something wrong. I'm also new to laying out components this deep so I might not even be doing the entire layout correctly. What am I missing?
Well you are emitting the showModel event from BaseModal but you are not listening for it on the parent and forwarding it+listening on grandparent (ProductForm)
But the main problem is :visible.sync="show" in BaseModal. It is same as if you do :visible="show" #update:visible="show = $event" (docs). So when the Dialog is closed, PrimeVue emits update:visible event which is picked by BaseModal component (thanks to the .sync modifier) and causes the mutation of the show prop inside BaseModal and the error message...
Remember to never use prop value directly with v-model or .sync
To fix it, use the prop indirectly via a computed with the setter:
BaseModal
<template>
<div>
<Dialog :header="header" :visible.sync="computedVisible" :modal="true" :closable="true">
<slot />
</Dialog>
</div>
</template>
<script>
export default {
props: {
show: Boolean,
header: String,
},
computed: {
computedVisible: {
get() { return this.show },
set(value) { this.$emit('update:show', value) }
}
},
};
</script>
Now you can add same computed into your ProductForm component and change the template to <BaseModal :show.sync="computedVisible" :header="product.name"> (so when the ProductForm receives the update:show event, it will emit same event to it's parent - this is required as Vue event do not "bubble up" as for example DOM events, only immediate parent component receives the event)
Final step is to handle update:show in the ProductList:
<ProductForm :product="slotProps.data" :show="showModal(slotProps.data.id)" #update:show="toggleModal(slotProps.data.id)"/>

Custom wrapper component with props and v-model

I am using the vue-select library: https://github.com/desislavsd/vue-select in my app.
Since I am using a lot of them I just want to make a separate wrapper component, but now my selects don't work. Vue somehow doesn't recognize the props. How can I make my v-select a separate reusable component that can accept it's normal props and worK?
This is my Select component:
<template>
<div>
<v-select/>
</div>
</template>
<script>
export default {
name: "Select",
}
</script>
And this is how I am using it:
<Select as="role" placeholder="Assesor" v-model="value1" :from="roles" :key="roles.role" />
export default {
name: "Admin",
components: {
Header,
Select
},
data() {
return {
value1: [],
selected: {
role: ''
},
roles: [
{ role: "Assesors" },
{ role: "Finance" },
{ role: "Sales" }
]
};
}
};
This is more complex than it seems. You expect the props passed to the custom component to be applied to the inner v-select but Vue doesn't know that. Someone else might expect them to go on the <div>.
v-bind="$attrs"
Props are not automatically passed to a child element. To do that, you need:
<v-select v-bind="$attrs" />
v-model
Now the props are applied to the element you choose. But since v-model is a special prop from the parent with hidden functionality, it's not passed properly without some extra preparation. You'll have to code a v-model directly on the child:
<v-select v-bind="$attrs" v-model="model" />
Computed setter
The parent's v-model passes down a value prop. Create a computed setter in the custom element (I'm calling it model) to use with the child's v-model:
computed: {
model: {
get() { return this.$attrs.value },
set(val) { this.$emit('input', val) }
}
}
Here is the updated demo with a custom wrapper component using selected and as="role::role"

How to re-render a component (Vue) with its data (and state) intact?

Here's how my code is structured: parent component shuffles through child components via v-if directives, one of the child components is using a state to define its data. Everything works except when I switch between the child components. When I get back, no data can be shown because the state has become null.
Parent component:
<template>
<div>
<Welcome v-if="view==0" />
<Courses v-if="view==1" /> //the component that I'm working on
<Platforms v-if="view==2" />
</div>
</template>
Courses component:
<template>
<div>Content</div>
</template>
<script>
export default {
name: 'Courses',
computed: {
...mapState([
'courses'
])
},
data () {
return {
courseList: [],
len: Number,
}
},
created () {
console.log("state.courses:")
console.log(this.courses)
this.courseList = this.courses
this.len = this.courses.length
},
}
</script>
Let say the default value for "view" is 1, when I load the page, the "Courses" component will be shown (complete with the data). If I click a button to change the value of "view" to 0, the "Welcome" component is shown. However, when I tried to go back to the "Courses" component, the courses component is rendered but is missing all the data.
Upon inspection (via console logging), I found that when the "Courses" component was initially rendered, the state was mapped correctly and I could use it, but if I changed the "view" to another value to render another component and then changed it back to the original value, the "Courses" component still renders but the state became undefined or null.
EDIT: Clarification.
Set courseList to a component name and use <component>
and set some enum
eg.
<template>
<component :is="this.viewState" />
</template>
<script>
export default {
// your stuff
data() {
return {
viewState: 'Welcome',
}
},
methods: {
getComponent(stateNum) {
const States = { '1': 'Welcome', '2': 'Courses', '3': 'Platforms' }
Object.freeze(States)
return States[stateNum]
}
},
created() {
// do your stuff here
const view = someTask() // someTask because I don't get it from where you're getting data
this.viewState = this.getComponent(view)
}
}
</script>
I don't actually understood correctly but here I gave some idea for approaching your problem.

How to pass values between parent and child components in vue?

I defined two different components:
'permissionTitle':customTitle,
'permissionItem':customItem,
in main template they are organized like this:
<permissionTitle content="品牌商管理">
<permissionItem>查看列表</permissionItem>
<permissionItem>添加</permissionItem>
<permissionItem>修改</permissionItem>
<permissionItem>删除</permissionItem>
</permissionTitle>
Now I want to pass values from permissionItem to permissionTitle and vice versa.
How to can I do this?
In permissionTitle.vue:
<template>
<div id="root">
<Checkbox>
<span>{{content}}</span>
</Checkbox>
<slot></slot>
</div>
</template>
In permissionItem.vue:
<template>
<Checkbox #on-change="change">
<slot></slot>
</Checkbox>
</template>
You can do this with v-model.
Add a model and prop to your child component, something like this:
Vue.component('permissionItem', {
model: {
prop: 'value',
event: 'change'
},
props: {
value: String
},
methods: {
// or however this value changes in your component
changeValue(newValue) {
this.value = value;
$emit('change', this.value;
....
And instantiate it like this:
<permissionTitle content="品牌商管理">
<permissionItem :v-model="value1">查看列表</permissionItem>
<permissionItem :v-model="value2">添加</permissionItem>
<permissionItem :v-model="value3">修改</permissionItem>
<permissionItem :v-model="value4">删除</permissionItem>
</permissionTitle>
variables 'valueN' are now available in your parent component.
Vue's documentation on this topic is a little lacking, but its basically here: https://v2.vuejs.org/v2/guide/components-custom-events.html

Categories