Can I pass props to a child component and use the data in the parent component in Vue - vue.js

I would like to pass data to a child component and render that same data in the parent components. Also I would like to use the data in a function and not simply render it in the child. When I pass the props in this example it no longer renders the tags with the data
<template>
<div class="hello">
<div v-for="(section, index) in sections" :key="index">
<p>{{section.name}}</p>
<p>{{section.type}}</p>
</div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
sections: [
{
name: "scoop",
type: "boulder"
},
{
name: "pew pew",
type: "roped"
}
]
};
},
props: ["sections"]
};
</script>

You can't use the same word/property name (sections in your case) for data and props.

I assume your code is the parent component:
// parent.vue
<template>
<div class="hello">
<div v-for="(section, index) in sections" :key="index">
<p>{{section.name}}</p>
<p>{{section.type}}</p>
</div>
<my-child-component :sections="sections" />
</div>
</template>
<script>
import MyChildComponent from '~/components/MyChildComponent.vue'
export default {
name: "HelloWorld",
components: {
MyChildComponent
},
data() {
return {
sections: [
{
name: "scoop",
type: "boulder"
},
{
name: "pew pew",
type: "roped"
}
]
}
},
methods: {
consoleSections() {
console.log(this.sections) // the way to use data in function
}
}
}
</script>
// MyChildComponent.vue
<template>
<div class="hello">
<div v-for="(section, index) in sections" :key="index">
<p>{{section.name}}</p>
<p>{{section.type}}</p>
</div>
</div>
</template>
<script>
export default {
name: "ChildHelloWorld",
props: ['sections'],
methods: {
consoleSections() {
console.log(this.sections) // the way to use data in child
}
}
}
</script>
Take a look in the vue guide about component: https://v2.vuejs.org/v2/guide/components.html

Related

function not updating vue property

I have this component:
<template>
<div class="hello">
<div>
My prop: {{ myprop }}?
</div>
<div>
<button class="fas fa-lock-open lock" #click="changeText()">Click</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'StartPage',
props: {
myprop: {
type: String
}
},
model: {
prop: 'myprop',
event: 'click'
},
methods: {
changeText () {
this.$emit('click', 'sometext')
console.log('this.myprop', this.myprop)
}
}
})
</script>
Im using vue v3. Everytime I click on the button, I still see the text "My prop: ?" in the browser.
And in the console I can see: "this.myprop undefined" every time I click on the button.
What am I doing wrong?
As per my understanding, You are trying to update the prop text on click of button from the child component. If Yes, you can achieve it simply by emitting a new text and updating that in the parent component.
Live Demo :
const ShowPropText = {
template: `<div class="hello">
<div>
My prop: {{ myprop }}
</div>
<div>
<button class="fas fa-lock-open lock" #click="changeText()">Click</button>
</div>
</div>`,
props: ['myprop'],
methods: {
changeText() {
this.$emit('click-event', 'sometext')
}
}
}
const app = Vue.createApp({
components: {
'show-prop-text': ShowPropText
},
data() {
return {
text: 'This is default text'
}
},
methods: {
methodCall(e) {
this.text = e;
}
}
})
app.mount('#app')
<script src="https://cdn.jsdelivr.net/npm/vue#next"></script>
<div id="app">
<show-prop-text :myprop="text" #click-event="methodCall"></show-prop-text>
</div>

Vue: Toggle Button with Data from Child to Parent

I have a parent component that has numerous containers. Each container has an image and some buttons.
I have over simplified the parent and child components below. When a button is clicked that is within the child component, I would like to toggle the class on an element that is in the parent container. I would like to effect each image individually, not globally. How do I do this?
parent:
<template>
<div>
<div :class="{ active: mock }">
<img src="/path">
</div>
<toggleButtons/>
</div>
<div>
<div :class="{ active: mock }">
<img src="/path">
</div>
<toggleButtons/>
</div>
</template>
<script>
import toggleButtons from './toggleButtons'
export default {
name: "parent",
components: {
toggleButtons
}
};
</script>
child:
<template>
<div class="switch-type">
<a #click="mock = false">Proto</a>
<a #click="mock = true">Mock</a>
</div>
</template>
<script>
export default {
name: "toggleButtons",
data() {
return {
mock: false
}
}
};
</script>
Oversimplified example of how you can pass data from child to parent:
Child:
<a #click="$emit('mockUpdated', false)">Proto</a>
<a #click="$emit('mockUpdated', true)">Mock</a>
Parent (template):
<toggleButtons #mockUpdated="doSomething" />
Parent(methods):
doSomething(value) {
// value will be equal to the second argument you provided to $emit in child
}
EDIT: (toggling the class for each individual container):
I would probably create a new component for the container (container.vue), pass a path as a prop :
<template>
<div>
<div :class="{ active: mock }">
<img :src="path">
</div>
<toggleButtons #mockUpdated="doSomething" />
</div>
</template>
<script>
export default {
props: {
path: String,
},
data() {
return {
mock: false
}
},
methods: {
doSomething(value) {
this.mock = value;
}
}
}
</script>
and then in Parent.vue, you can import the container component and use it like:
<template>
<Container path="/path-to-file.jpg" />
<Container path="/path-to-file.jpg" />
</template>
There is nothing to do with slots in your case. You need "emit event" to parent from button, and pass mock data to update img states. Slot is a very different thing. There are multiple ways to achive your goal. One way can be something like this:
parent
<AppImage v-for="img in imageData" :key="img.id" :image="img"/>
data() {
iamges: ["yourPath1", "yourPath2"]
},
computed: {
imageData() {
// adding ID is almost always a good idea while creating Vue apps.
return this.images.map(x => {
id: Math.floor(Math.random() * 1000),
path: x
})
}
}
Image.vue
<img :path="image.path" :class={ active: mock} />
<toggleButtons #toggled=changeImageState />
props: [image],
data() {
mock: ''
},
methods: {
changeImageState(event) {
this.mock = event
}
}
ToggleButtons.vue
<a #click="toggleMock(false)">Proto</a>
<a #click="toggleMock(true)">Mock</a>
emits: ['toggled']
methods: {
toggleMock(val) {
this.$emits('toggled', val)
}
}
Please read the code and let me know if you have any question.

Watching properties in child component does not work

I have 2 components, a parent and a child. I want to modify the value of a prop in my parent component (by calling a method when a button is clicked) and send it to the child component. In the child component, I want to watch for changes in my prop, so that anytime it changes, it does something (for testing purposes, I tried to console.log() the prop).
Parent:
<template>
<div>
<h5>Your Feeds</h5>
<div id="feeds">
<div class="card" v-for="feed in feeds">
<div class="card-body" :id="feed['_id']" >
{{ feed['name'] }}
<button v-on:click="loadFeed(feed['_id'])">Button</button>
</div>
</div>
</div>
</div>
</template>
<script>
import GridComponent from "./GridComponent";
export default {
name: "FeedsListComponent",
data() {
return {
feeds: []
}
},
mounted() {
axios
.get("/getFeeds")
.then(response => (this.feeds = response.data))
.catch(error => console.log(error))
},
methods: {
loadFeed(id) {
this.feedId = id
}
},
components: {
GridComponent
}
}
</script>
Child:
<template>
<div id="grid">
<v-grid
theme="compact"
:source="rows"
:columns="columns"
></v-grid>
</div>
</template>
<script>
import VGrid from "#revolist/vue-datagrid";
export default {
name: "Grid",
props: ['feedId'],
data() {
return {
columns: [],
rows: [],
};
},
watch: {
feedId: function(val, oldVal) {
console.log(val)
console.log(oldVal)
console.log(this.feedId)
//here I want to send an ajax request with feedId to one of my controllers in order to get
//the data needed for rows and colums
}
},
components: {
VGrid,
},
};
</script>
I put together a sample that is working in order to help you diagnose why yours isn't working:
Parent.vue
<template>
<div class="parent">
<h3>Parent</h3>
<div class="row">
<div class="col-md-6">
<button class="btn btn-secondary" #click="incrementCounter">Change parent message</button>
</div>
</div>
<child :propMessage="message" />
</div>
</template>
<script>
import Child from '#/components/stackoverflow/watch-prop/Child'
export default {
components: {
Child
},
data() {
return {
counter: 0
}
},
computed: {
message() {
return 'Message' + this.counter;
}
},
methods: {
incrementCounter() {
this.counter++;
}
}
}
</script>
Child.vue
<template>
<div class="child">
<hr>
<div class="row">
<div class="col-md-6">
<label>Message in child from watched prop:</label>{{ dataMessage }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
propMessage: {
type: String,
required: true
}
},
data() {
return {
dataMessage: this.propMessage
}
},
watch: {
propMessage(newMessage) {
this.dataMessage = newMessage;
}
}
}
</script>
<style scoped>
label {
font-weight: bold;
margin-right: 0.5rem;
}
</style>

Vuejs passing function from parent to child

I have a beginner question about passing function from parent to child. In my example, I want to to use the child more times and sometimes it should to do someting else v-on:focus. How can i do that? There are options to pass it with prop but i don't know how and i think it's not good to do it ? Maybe with EventBus and if yes then how ? I want to know the right way how to do it in VueJs.
Here is the Parent Component:
import Child from "./child.js";
export default {
name: "app",
components: {
Child
},
template: `
<div>
<child></child>
<child></child>
<child></child>
</div>
`
};
And here is the child Component:
export default {
name: "test",
template: `
<div class="form-group">
<div class="input-group">
<input v-on:focus="functionFromChild">
</div>
</div>
`,
methods: {
functionFromChild() {
//run the function from parent
}
}
};
You can pass the function as any other prop
import Child from "./child.js";
export default {
name: "app",
components: {
Child
},
methods: {
calledFromChild(id){
console.log(id)
}
},
template: `
<div>
<child :callback="calledFromChild" id="1"></child>
<child :callback="calledFromChild" id="2"></child>
<child :callback="calledFromChild" id="3"></child>
</div>
`
};
And then in the child
export default {
name: "test",
props: ["callback", "id"],
template: `
<div class="form-group">
<div class="input-group">
<input v-on:focus="() => this.calledFromChild(this.id)">
</div>
</div>
`,
}
I'm also adding an id to the child so you know which child is making the call.
But this is not a good idea. You should use emit from your child to send an event, and listen to it from the parent.
In the child
export default {
name: "test",
template: `
<div class="form-group">
<div class="input-group">
<input v-on:focus="handleFocus">
</div>
</div>
`,
methods: {
handleFocus() {
this.$emit('focusEvent')
}
}
};
And in the parent
<child #focusEvent="handleFocusFromChild"></child>
A working example here
This should work:
const Child = {
template: `
<div class="form-group">
<div class="input-group">
<input v-on:focus="functionFromChild">
</div>
</div>
`,
props: {
functionFromParent: Function
},
methods: {
functionFromChild: function() {
this.functionFromParent();
}
},
data() {
return {
message: 'Oh hai from the component'
}
}
}
const App = {
template: `
<div>
<h1>Quick test</h1>
<p>{{ message }}</p>
<Child :functionFromParent="functionOnParent"/>
<Child :functionFromParent="functionOnParent"/>
<Child :functionFromParent="functionOnParent"/>
</div>
`,
components: {Child},
methods: {
functionOnParent: function(){
console.log("there we go");
}
},
data() {
return {
message: 'Hello'
}
}
}
new Vue({
render: h => h(App),
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>
If you're trying to call a function in the parent from the child component, then try
this.$parent.parentMethod()
This will invoke the method in parent component.

Click button in child component to pass data to parent (via one other child)

Is it possible to pass data by clicking on the button in nested child component, pass it to anoter child component that is level above and then to parent component?
My components are nested this way:
Parent
----Child
---------Child 2 (with the button that should trigger action)
So Child 2 look like this:(I added the addList() method that should push the variable to the list that is in the parent)
<template>
<li class="movie">
<div class="movie__image">
<img class="movie__poster" :src="imageURL" :alt="movie.Title"/>
<button class="movie__more" :href="`https://www.imdb.com/title/${movie.imdbID}`" target="_blank">Add</button>
</div>
</li>
</template>
<script>
export default {
name: 'movie-list-item',
props: {
movie: {
type: String,
required: true
}
},
methods: {
addList(event) {
this.$emit('clicked', this.movie.imdbID)
}
}
</script>
Child 1
<template>
<ul class="grid">
<movie-list-item li v-for="movie in movies" :movie="movie" :key="movie.imdbID" />
</ul>
</template>
<script>
import MovieListItem from './MovieListItem'
export default {
name: 'movie-list',
components: {
MovieListItem
},
props: {
movies: {
type: Array,
required: true
}
}
}
</script>
Parent:(I want to push the item from child 2 component to picked list.
<template>
<div>
<movie-list :movies="movies"></movie-list>
</div>
</template>
<script>
import MovieList from './components/MovieList'
export default {
name: 'app',
components: {
MovieList,
},
data () {
return {
show:false,
movies: [],
picked:[]
}
}
</script>
Without using any kind of state management, you'll have to emit two time.
Otherwise, you can use Vuex and have a state management.
Child1
<template>
<ul class="grid">
<movie-list-item li v-for="movie in movies" :movie="movie" :key="movie.imdbID" #clicked="handleClick(movie.imdbID)" />
</ul>
</template>
<script>
import MovieListItem from './MovieListItem'
export default {
.....
methods: {
handleClick(imdbID) {
this.$emit("clicked", imdbID);
}
}
}
</script>
Parent
<template>
<div>
<movie-list :movies="movies" #clicked="handleClick($event)"></movie-list>
</div>
</template>
<script>
import MovieList from './components/MovieList'
export default {
...
data () {
return {
show:false,
movies: [],
picked:[]
}
},
methods: {
handleClick(imdbID) {
this.picked.push(imdbID);
}
}
</script>