Accessing called component data in main template - javascript

Hopefully this is an easy fix and i can just chalk it up to my inexperience with Vue
In my main template, I can use v-for to access any objects in the templates data return and I'm successfully loading a separate component in my html
However, when I try to access info from that external component outside of my component call it doesn't show.
I'm trying to create something that I can turn into a modal which would be separate from all imported components but still be able to access data from any component in my main template's modal
How can I access data from any called component in my main template?
<template>
<!--this content shows-->
<subItem-component></subItemComponent>
<!--this doesn't-->
<p v-for="subItem in subItems>{{ subItem.title }}</p>
</template>
subitemComponent
data() {
return {
subItems: [],
}
}

You cannot access the scope of a child component within the parent. Usually, you would load the required data in the parent, if you are needing it there, and then pass it along to the child component as a prop like this:
ParentComponent:
<template>
<sub-item-component sub-item="subItem"></sub-item-component>
<p v-for="subItem in subItems>{{ subItem.title }}</p>
</template>
<script>
import SubItemComponent from '...';
export default {
name: 'ParentComponent',
components: {
SubItemComponent
},
data () {
return {
subItems: []
};
}
}
</script>
SubItemComponent:
<template>
{{ subItem }}
</template>
<script>
export default {
name: 'SubItemComponent',
props: {
subItem: {
type: Array,
required: true
}
}
}
</script>
You could also use a Vuex store to persist the data in one place. With this approach you would have access to the data from each component and do not have pass the data from component to component. To use a store like Vuex, you need to define a Vuex state and at least one mutation method to add the data.
If you want to communicate from the child up to the parent, the way to go would be by using events. You would use a listener on the child component in the parent component like this:
<sub-item-component #new-sub-item="handleNewSubItem"></sub-item-component>
You need to define a method handleNewSubItem in the parent, which is reacting each time the new-sub-item event is thrown in the child component.
The event can then be thrown within the script part of the child component like this:
const subItem = { test: 123 };
this.$emit('new-sub-item', subItem);

You don't. Your options are:
The parent component needs to manage the data and pass it down to its children via props. Child components can then subsequently emit events which the parent can listen for and modify its state.
Use a state handler outside of both the components like Vuex or Apollo (if using GraphQL).

Related

Avoid mutating a prop directly: Instead, use a data or computed property based on the prop's value. Prop being mutated: "items"

I am passing the param for breadcrumbs in array format for each page.
I get the following warning
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:
"items"
Breadcrumbs Component
<template>
<vs-row class="vs-breadcrumb-row" vs-type="flex" vs-justify="space-around">
<vs-col
type="flex"
vs-justify="center"
vs-align="center"
vs-lg="12"
vs-sm="12"
vs-xs="12"
code-toggler
>
<vs-card class="br-0">
<h4 class="card-title mb-0 d-flex">
<vs-row vs-justify="space-between" vs-align="center">
{{ pageTitle }}
<div class="d-flex align-items-center">
<vs-breadcrumb separator="chevron_right"
:items="breadcrumbLinks"
></vs-breadcrumb>
</div>
</vs-row>
</h4>
</vs-card>
</vs-col>
</vs-row>
</template>
<script>
export default {
name: "Breadcrumbs",
props: {
pageTitle: {
type: String
},
breadcrumbLinks: {
type: Array
}
}
};
</script>
I am getting the following error.
The layout structure is.
Dashboard - sending the array param to default layout and default layout pass it to breadcrumb
Default Layout
Breaducrumb
Dashboard Component
export default {
name: "Dashboard",
components: {
DefaultLayout
},
data: () => ({
breadcrumbLinks: [
{
title: global.vm.$t('breadcrumbs.home'),
url: ""
},
{
title: global.vm.$t('breadcrumbs.dashboard'),
active: true
}
],
})
};
The props sent to children should not be modified outside it's parent component.
Dashboard - sending the array param to default layout and default layout pass it to breadcrumb
Default Layout
Breaducrumb
Your breadcrumbLinks prop is been changed outside of Dashboard component where it was created, so, you are probably making changes on this prop on Default Layout or Breaducrumb components.
If you avoid mutating props outside its components you'll avoid this warnings.
But it's a warning, not an error, what it says is that if the parent component gets mutated, the child components, in this case Default Layout and Breaducrumb will be updated with the values previously or initially set on the parent component.
So any change you do to a prop on any child component will be lost, this may not be a problem and make your code work perfectly but it is certainly a pattern that should be avoid.
If you want to mutate props:
Send a $emit to the parent that has this props as data and performe the changes there.
Here you can read about custom events - VueDocs
Use a global state manager as Vuex
In your case you may not need any of this, please, show us the function that modifies breadcrumbLinks prop.
It should be somewhere in DefaultLayout or on Breadcrumb components, but for sure it is not on the code you posted.

Event Bus to communicate between siblings. Do i need a parent and its data sent as prop?

I watched a video to understand how to use event bus to communicate between siblings, and in the video there was a parent with some data that was sent to the childs as a prop, then a method in one of the childs modified that prop and used an event bus to send it to the other child.
I thought, whats the point of the prop? Why cant i just use the siblings own data? And thats what i did:
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
export const bus = new Vue() //Event Bus
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<ComponenteA></ComponenteA>
<ComponenteB></ComponenteB>
</div>
</template>
<script>
import ComponenteA from './components/ComponenteA.vue'
import ComponenteB from './components/ComponenteB.vue'
export default {
name: 'App',
components:{
ComponenteA,
ComponenteB
}
}
</script>
ComponentA.vue
<template>
<div>
<h1 #click="changeTitle">Componente A</h1>
</div>
</template>
<script>
import { bus } from '../main'
export default {
name: 'ComponenteA',
data(){
return{
title: ''
}
},
methods:{
changeTitle(){
this.title = 'Title emitted from A a B'
bus.$emit('titleChanged',this.title)
}
}
}
</script>
ComponentB.vue
<template>
<div>
<h1>Componente B -> {{title}}</h1>
</div>
</template>
<script>
import { bus } from '../main'
export default {
name: 'ComponenteB',
data(){
return{
title: ''
}
},
created(){
bus.$on('titleChanged', (payload) =>{
this.title = payload
})
}
}
</script>
Is there anything wrong with my code? Is there a reason for using the parent data that im failing to see?
The reason that the data should start in the parent and get passed down to the children is because of a couple things:
The reactivity system in Vue.js is built around having your data in a single place (a "single source of truth"), then passing that data to wherever it is needed. Now, if only a single component needs the data, you'll just store that data on the component. But the data is needed in multiple components, you'll want to store it in a parent and then pass it to the children. This becomes particularly obvious if you need to start using Vuex.
If for some reason you need to change, say, the name of the data (e.g., pageTitle instead of title), it becomes far easier to trace where the data came from if it always comes from a parent. Relying on the event bus across siblings can become rather brittle as the project grows larger.
So, in your case, the title should really exist in the data of your App.vue component. Each of the children would receive it as a prop. Then, if the title was changed, $emit() an event that App.vue is listening for. That event would change the data in App.vue.
Take a look at this question for more details:
vuejs update parent data from child component

Access input from dynamically loaded component in parent component in Vuejs

I've created a wizard-type flow in Vuejs using dynamically loaded components, where the Save button is in the parent component, and all of the text fields are in the loaded components. When the Save button is clicked, I need two things to happen:
1) The next component is loaded (currently working)
2) Get data from the text field(s) in the child component and write them to my Vuex store.
Here is the code for the parent component:
<template>
<div>
<component :is="selectedComponent"></component>
<span #click="changeComponent(selectedComponent)" class="action button save">Save</span>
</div>
</template>
<script>
import auth from '#/api//auth'
import Establishment from '#/onboarding/components/OnboardingEstablishment.vue'
import Account from '#/onboarding/components/OnboardingAccount.vue'
import Vendor from '#/onboarding/components/OnboardingVendors.vue'
import Location from '#/onboarding/components/OnboardingLocation.vue'
import Menu from '#/onboarding/components/OnboardingMenu.vue'
export default {
data () {
return {
accountName: '',
selectedComponent: 'account'
}
},
components: {
establishment: Establishment,
account: Account,
vendor: Vendor,
location: Location,
appMenu: Menu
},
methods: {
changeComponent (current) {
// Mapping object to map what the next component should be
// when changing the dynamic component.
const componentFlow = {
account: 'establishment',
establishment: 'location',
location: 'vendor',
vendor: 'appMenu'
}
// Get the name of the next component.
var nextComponent = componentFlow[current]
this.selectedComponent = nextComponent
// We also need to update Vuex with the value from the text field.
},
updateState () {
// Write the data from the element to the state.
}
},
mounted () {
// Get name of logged in user.
auth.getAccountDetails()
.then(response => {
this.accountName = response.data.email
})
}
}
</script>
And as an example, here is OnboardingAccount.vue, one of the dynamically loaded components:
<template>
<div>
<h1>Hey {{ accountEmail }}</h1>
<p>Let's start at the top.</p>
<p>What do you want to name your Account?</p>
<input :value="accountName" type="text" />
<p>Note: Your account name is the top level of your management. Additional teams or establishments can be added under this account name.</p>
</div>
</template>
<script>
import auth from '#/api/auth'
import Establishment from '#/onboarding/components/OnboardingEstablishment.vue'
import Account from '#/onboarding/components/OnboardingAccount.vue'
import Vendor from '#/onboarding/components/OnboardingVendors.vue'
import Location from '#/onboarding/components/OnboardingLocation.vue'
export default {
data () {
return {
accountEmail: '',
accountName: ''
}
},
components: {
establishment: Establishment,
account: Account,
vendor: Vendor,
location: Location
},
mounted () {
// Get name of logged in user.
auth.getAccountDetails()
.then(response => {
this.accountEmail = response.data.email
})
}
}
</script>
What I need to do is somehow be able to access the value from the OnboardingAccount component when I click on the Save button in the parent Onboarding component so that I can write the date to the Vuex store, either in my changeComponent method, or elsewhere. In looking at the debugger, I don't see that value anywhere. How can I do what I need to do?
I'd keep the state for every component in the parent component and pass them down as props, then on input events #change or #blur (or other) emit an event. This is is basically standard component communication, but since you're using dynamic components this gets a bit more tricky, in passing props and events.
To pass props to dynamic components somewhat cleanly you can use v-bind, but on a whole object instead of per variable/property, like here. The properties of this object can be objects containing props for each dynamic component, also this keeps the component tag clean. Then each dynamic child component can pick out the property from the props object that is meant for it, and use that data. For the event back from the children set a generic event on the component tag, name e.g. childEvent, to which each dynamic component would report a change of its state, as in it would return the changed props, which would update the state in the parent component. Saving should then be simple as all the data is in the parent component.
Side note:
You're using dynamic components well but you can bring it to the next level if you utilize webpack's dynamic imports along with it, then you won't need to manually import each one and declare them as components. Take a look at this article.

Vue.js dynamic component with dynamic data

I'm trying to use the <component> element to dynamically display a chosen component. Each of these displayed components can take one of any number of data objects. Something like:
<div id="containers">
<component v-bind:is="currentView"></component>
</div>
var myVue = new Vue({
el:"#containers",
data:{
currentView: "my-component-one",
currentData: {...}
},
method: {
changeView: function(){
//change this.currentView
//change this.currentData
}
}
});
However, the Vue documentation says the v-bind:is attribute can be used to pass either a component name or the options object.
It is unclear how I would conditionally get an object of values for that component to use and also conditionally change which component is shown.
I am very green with using Vue (coming fresh off a knockout kick) so perhaps I am simply misunderstanding the intention of the component tag.
you can simply use v-bind
html
<component v-bind:is="currentView" v-bind="data"></component>
script
data()
{
return {
data: {
currentData: "example"
}
}
}
and it will pass currentData down to child. You can also add other properties along with it, including is.
If you need to change the component along with props, then you just change the data property, or whatever you want to call it.
https://codesandbox.io/s/7w81o10mlx
This example might help you understand it. https://jsfiddle.net/jacobgoh101/mLbrj5gd/
For passing Component Name
If the component is global, you can pass the component name to v-bind:is
for e.g.,
Vue.component('test1', {
template: `<div>test1</div>`
})
HTML
<component is="test1"></component>
For passing option
A Vue component is literally just a Javascript object with specific properties
for e.g.,
<component v-bind:is="{
template: `<div>test2</div>`
}"></component>

Vuex store can be manipulated when passing state to component

Not quite sure if this is a post for stackoverflow but i really struggle with this one.
I'm using vuex and vue1. I love vuex because you can't/shouldn't change the store without mutations.
But now I'm passing a state object to a (child) component. When I'm changing the value in the child component. Vuex allows me to change it.
Is there a specific reason why this can be done or is this just bad practice?
Parent component
<child-component :pass-value="stateValueParent"> </child-component>
vuex: {
getters: {
stateValueParent: (state) => state.values.stateValueParent
}
}
Child component
export default {
props: [
'passValue'
],
let value = this.passValue
// This is changed but it should right?
value = 'newValue'

Categories