I am getting data from api which I want to pass as prop to a child component
Parent component
<template>
<div class="dashboard">
<items name="Admins" value={{data.items.hubs}} bgColor="#a80c0c" />
</div>
</template>
import Items from './Items.vue'
import { Admin } from '#/services/AdminService';
export default {
name: "AdminDashboard",
components: {
Items
},
setup(){
onMounted(() => {
showLoader(true);
Admin.getDashboardItems()
.then((response) => {
data.items = response.data.data
})
.catch((error) => {
})
.finally(() => {
showLoader(false);
});
});
return {
data
}
}
}
I have gotten the value I need from the api and passed it to data.items
If i display it on the parent component.
It works fine but on the child component
it does not work
Child Component
<template>
<div class="col-md-3">
<div class="items" :style="{ backgroundColor: bgColor }">
<div class="d-flex space-between">
<div></div>
<div>
<h5>{{ value }}</h5>
<span>{{ name }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Items",
props: ["bgColor", "value", "name"]
}
</script>
The child components display {{data.items.hubs}} instead of the value of hub
The data from the api
{"data":{"users":1,"incubatees":1,"hubs":2,"investors":1,"events":0,"admins":3,"programs":0}}
After some research
I found out I was not supposed to use {{}}
instead I should do it this way on the parent component
<items name="Hubs" :value="data.items.hubs" bgColor="#d79a2b" />
Related
I would like to pass data from my parent to child component and bind data to a input field through v-model to display data from my api call in parent component. But it seems to be problem when binding to input field i get this error message:
Unexpected mutation of "data" prop.eslintvue/no-mutating-props
Partent Component
<script lang="ts">
import { defineComponent,ref } from 'vue';
import axios from 'axios'
import ChildComponent from '../components/ChildComponent.vue';
export default defineComponent({
Component: { ChildComponent },
name: 'IndexPage',
setup() {
return {
fixed: ref(false),
data: []
};
},
mounted() {
this.getData();
},
methods: {
getData() {
axios.get('/api/Hotel/' + 2).then((response) => {
this.data = response.data;
this.fixed = true,
console.log(this.data);
});
}
},
components: { ChildComponent }
});
</script>
Child Component
<template>
<main>
<q-card class="height: 500px; width: 500px">
<q-card-section>
<div class="text-h6">Terms of Agreement</div>
<div class="q-pa-md">
<div class="q-gutter-md" style="max-width: 300px">
<div>
<q-input filled v-model="data.message" label="Filled" />
</div>
</div>
</div>
</q-card-section>
<q-separator />
<q-separator />
<q-card-actions align="right">
<q-btn flat label="Decline" color="primary" v-close-popup />
<q-btn flat label="Accept" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</main>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
props:['data'],
name: 'ChildComponent',
setup() {
return {
text: ref(''),
};
},
});
</script>
I have tried this to make mounted methods in my child components like this:
<div>
<q-input filled v-model="dataIn.message" label="Filled" />
</div>
export default defineComponent({
props:['data'],
name: 'ChildComponent',
setup() {
return {
text: ref(''),
dataIn:{}
};
},
mounted(){
this.dataIn = this.data
},
});
It seems to work but not optimal, i lost my data when i refresh my page. Anyone have a soulution ?
Props should be read readonly.
Your dataIn approach needs a watcher that will update your dataIn whenever your data props change
optionsApi:
export default defineComponent({
props:['data'],
name: 'ChildComponent',
data() {
text: '',
data: this.dataIn,
}
watcher: {
dataIn: (newValue,oldValue){
this.data = newValue
}
}
});
It seems that you want to make change to your data on your child component, you have to make it two-way binding. You should change your child code like this ( you are using custom q-input in your component and the attributes may differ a little but it is the same concept) :
<q-input
:value="value"
v-bind="$attrs"
v-on="$listeners"
#input="(v) => $emit('input', v)"
/>
and instead of using data prop you should change it to value :
props: {
value: {
type: [String], // multiple type also defenition accepted
default: "",
},
}
then in your parent simply use child component like this :
<your-child-component v-model="someData" />
If I understood you correctly, you need to emit event from child or to use computed property with getter/setter:
const { ref, onMounted, watch } = Vue
const app = Vue.createApp({
setup() {
const items = ref({})
const getData = () => {
items.value = ({id: 1, message: 'aaa'})
/*axios.get('/api/Hotel/' + 2).then((response) => {
this.data = response.data;
this.fixed = true,
console.log(this.data);
});*/
}
onMounted(async() => await getData())
// π react to chenges from child
const changed = (val) => {
items.value.message = val.message
}
return {
//fixed: ref(false),
items, changed
};
},
})
app.component('ChildComponent', {
template: `
<main>
<q-card class="height: 500px; width: 500px">
<q-card-section>
<div class="text-h6">Terms of Agreement</div>
<div class="q-pa-md">
<div class="q-gutter-md" style="max-width: 300px">
<div>
<!-- π listen to updating -->
<q-input filled v-model="text.message" #update:model-value="change" label="Filled" />
</div>
</div>
</div>
</q-card-section>
<q-separator />
<q-separator />
<q-card-actions align="right">
<q-btn flat label="Decline" color="primary" v-close-popup />
<q-btn flat label="Accept" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</main>
`,
props:['items'],
setup(props, {emit}) {
const text = ref(props.items)
// π emit event with changed value
const change = () => { emit('changed', text.value) }
// π watch for the props changes
watch(
() => props.items,
(newValue, oldValue) => {
text.value = newValue;
}
);
return {
text, change
};
},
})
app.use(Quasar)
app.mount('#q-app')
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/quasar#2.5.5/dist/quasar.prod.css" rel="stylesheet" type="text/css">
<div id="q-app">
{{items}}
<!-- π listen to child event -->
<child-component :items="items" #changed="changed"></child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#2.5.5/dist/quasar.umd.prod.js"></script>
I'm new to Vue and I created a list of items and each item has an edit-button. When I click the button, I open a modal window and pass the item's reactive data object to the modal (:data="itemData") to fill a form for editing the data.
When editing the data in the form, I don't want the original items in the list to change. Therefor I made a copy of the data prop (not sure if I did this correctly). It seems to work. When I save the changes, the list updates accordingly.
The problem is, this only works once. After editing and saving e.g. the 1st item in the list and then try to edit the 2nd item, nothing happens. I expected the edit form to update and show the 2nd item's data. I'm obviously doing something wrong, but I can't figure out what it is and how to do this properly. Please help.
Here's a very basic dummy for demonstration:
App.vue:
<script setup>
import { reactive } from 'vue';
import EditModal from './components/EditModal.vue'
import ListItem from './components/ListItem.vue'
const state = reactive({
items: [
{ id: 1, title: 'Item 1' },
{ id: 2, title: 'Item 2' }
],
currentItem: null,
showModal: false
});
function editItem(itemData) {
state.showModal = true;
state.currentItem = itemData;
}
function updateItem(itemData) {
state.items = state.items.map(item => item.id === itemData.id ? itemData : item);
}
</script>
<template>
<div>
<EditModal
v-if="state.currentItem"
:show="state.showModal"
:data="state.currentItem"
#save="itemData => updateItem(itemData)"
/>
<ListItem
v-for="itemData in state.items"
:data="itemData"
#edit="itemData => editItem(itemData)"
/>
</div>
</template>
ListItem.vue:
<script setup>
defineProps(['data']);
</script>
<template>
<div>
{{ data.title }}
<button #click="$emit('edit', data)">Edit</button>
</div>
</template>
EditModal.vue:
<script setup>
import { reactive, toRaw } from 'vue';
const props = defineProps(['show', 'data']);
const data = Object.assign({}, toRaw(props.data));
</script>
<template>
<div v-show="show">
<div class="modal">
<div class="modal-inner">
<input v-model="data.title">
<button #click="$emit('save', data)">Save</button>
</div>
</div>
</div>
</template>
For better solution (my opinion)
There is a getter and setter in 'computed' you could try to use it instead of using 'watch'
https://vuejs.org/guide/essentials/computed.html#writable-computed
<script setup>
import { reactive, computed } from 'vue';
const props = defineProps(['show', 'data']);
const state = reactive({ data: null });
const inputData = computed({
get() { return props.data; },
set(newVal) { state.data = newVal },
});
</script>
<template>
<div v-show="show">
<div class="modal">
<div class="modal-inner">
<input v-model="inputData">
<button #click="$emit('save', state.data)">Save</button>
</div>
</div>
</div>
</template>
Okay, I think I solved it. If someone has a better solution, please let me know.
EditModal:
<script setup>
import { toRaw, reactive, watch } from 'vue';
const props = defineProps(['show', 'data']);
let data = Object.assign({}, toRaw(props.data));
let state = reactive({ data: data });
watch(() => props.data, (selection, prevSelection) => {
data = Object.assign({}, toRaw(selection));
state.data = data;
});
</script>
<template>
<div v-show="show">
<div class="modal">
<div class="modal-inner">
<input v-model="state.data.title">
<button #click="$emit('save', state.data)">Save</button>
</div>
</div>
</div>
</template>
Parrent component
<progress-bar
:maxQuote = "maxQuotes"
:currentQuote="allQuotes.length" >
</progress-bar>
data: function() {
return {
allQuotes: [],
maxQuotes: 10
};
},
Progressbar Component
<template>
<div class="container">
<div class="progress">
<div class="progress-bar" :style="{'width': +90 + '%'}">{{current}} / {{max}}</div>
</div>
</div>
</template>
<script>
export default {
props: ["maxQuote", "currentQuote"],
data: function() {
return {
max: this.maxQuote,
current: this.currentQuote
};
}
};
</script>
Here I want to pass the length of my allQuotes[] array
maxQuote prop passed successfully but currentQuote not passed any number , even after array values are increased !
You are passing props, but then you assign them to reactive data() and you use those in your template. What happens, is that your props instantiate the data() props, but then they are not changing them anymore when the props change. You should just use the props inside your child component, like so:
<template>
<div class="container">
<div class="progress">
<div class="progress-bar" :style="{'width': +90 + '%'}">{{currentQuote}} / {{maxQuote}}</div>
</div>
</div>
</template>
<script>
export default {
props: ["maxQuote", "currentQuote"],
data: function() {
return {
};
}
};
</script>
Good afternoon, I have two child components Header and Pagination. In Header, I have an input search engine and two inputs (title and body) in order to be able to add a post to Pagination. I managed to transfer the search value to the Pagination component, but I donβt know how to transfer the value from two inputs (title, body). I use to transfer the event bus. Help me please pass the value of the two inputs (title, body) into the Pagination component when you click the AddPost button.
My code on GitHub
Screenshot of app
My code of component Header:
<template>
<div class="header">
<input type="text" v-model="search" class="header_input_search" placeholder="Search" #input="saveMessage" />
<img src="src/assets/milk.png">
<div class="header_div_inputs">
<input type="text" v-model="createTitle" class="created"/>
<p><input type="text" v-model="createBody" class="createBody"/></p>
</div>
<button #click="addPost()" class="addPost">AddPost</button>
</div>
</template>
<script>
import axios from 'axios';
import {eventEmitter} from './main'
export default {
name: 'Header',
data () {
return {
search: '',
createTitle: '',
createBody: '',
}
},
methods:{
saveMessage(){
eventEmitter.$emit('messageSave', this.search)
},
}
}
</script>
My code of component Pagination:
<template>
<div class = "app">
<ul>
<li v-for="(post, index) in paginatedData" class="post" :key="index">
<router-link :to="{ name: 'detail', params: {id: post.id, title: post.title, body: post.body} }">
<img src="src/assets/nature.jpg">
<p class="boldText"> {{ post.title }}</p>
</router-link>
<p> {{ post.body }}</p>
</li>
</ul>
<div class="allpagination">
<button type="button" #click="page -=1" v-if="page > 0" class="prev"><<</button>
<div class="pagin">
<button class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}"
#click="page=n-1">{{ n }} </button>
</div>
<button type="button" #click="page +=1" class="next" v-if="page < evenPosts-1">>></button>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import {eventEmitter} from './main'
export default {
name: 'app',
data () {
return {
current: null,
page: 0,
visiblePostID: '',
pSearch: ''
}
},
mounted(){
this.$store.dispatch('loadPosts')
},
computed: {
...mapState([
'posts'
]),
evenPosts: function(posts){
return Math.ceil(this.posts.length/6);
},
paginatedData() {
const start = this.page * 6;
const end = start + 6;
return this.filteredPosts.slice(start, end);
},
filteredPosts() {
return this.posts.filter((post) => {
return post.title.match(this.pSearch);
});
},
},
created(){
eventEmitter.$on('messageSave', (string) => {
this.pSearch = string
})
}
}
</script>
You can wrap title and body in an object
addPost() {
const post = {
title: this.createTitle,
body: this.createBody
}
eventEmitter.$emit('postAdd', post)
}
and then listen as normal
created(){
eventEmitter.$on('postAdd', (post) => {
console.log(post)
// do whatever you want
})
}
I have not worked on vue js but agreed with #ittus answer. You can make an object consisting of your required data which you want to share across the component and pass it as an event data.
I Created my API with PHP and here is the link: https://monstajams.co/streaming/rest/api/album/read.php
But anytime i put it in my Vue.js (Home.vue) file using axios No data is displayed on the front-end.
Here is my code below:
<ul class="ta-track-list" v-if="faqs && faqs.length">
<li class="ta-track-card column col-2 flex-column" v-for="faq of faqs">
<div class="inner">
<div class="artwork" role="link">
<span role="link" style="background-image: url(http://localhost/mymusic/assets/images/artwork/Wizkid-Soco.jpg);">
</span>
<div class="hover flex align-center justify-center">
<button id="webutton" class="ta-secondary play" onclick='playSong()'>
<i class="material-icons">play_arrow</i>
</button>
</div>
</div>
<div class="info">
<div class="title white-primary-hover" role="lin">{{ faqs }}</div>
<div class="username light-white-hover" role="link">{{ faq.artist }}</div>
<div class="released">{{ faq.duration }}</div>
</div>
</div>
</li>
</ul>
<script>
import axios from 'axios';
export default {
name: 'home',
data: () =>({
faqs: [],
errors: []
}),
created() {
axios.get('https://monstajams.co/streaming/rest/api/album/read')
.then(response => {
this.faqs = response.data;
})
.catch(e => {
this.errors.push(e)
})
}
}
</script>
The problem is your code incorrectly assumes axios.get() resolves to the raw response, but it actually resolves to a response wrapper, where the raw response is contained in a data subproperty of the wrapper, which coincidentally has the same name as the target property within the response.
You can either change your Axios response handler to get the inner data field:
axios.get('https://monstajams.co/streaming/rest/api/album/read')
.then(response => {
// this.faqs = response.data; // response.data is the raw response, but you need the array within the response (also named "data")
this.faqs = response.data.data;
})
demo
Or leave your frontend alone, and update your PHP backend to send only the array in the response:
// FROM THIS RESPONSE:
{
data: [/* PLAYLIST DATA */]
}
// TO THIS RESPONSE:
[/* PLAYLIST DATA */]
You are not updating your data accordingly to Vue docs.
For reactive changes see this document.
In the example below i update my list before Vue is mounted so rendering can occur accordingly.
let vm = new Vue({
el: "#app",
data: {
todos: []
},
methods: {
updateList() {
axios.get('https://monstajams.co/streaming/rest/api/album/read')
.then(res => {
res.data.data.forEach(item => {
Vue.set(vm.todos, vm.todos.length, {
text: item.title,
done: true
})
})
})
}
},
beforeMount() {
this.updateList()
},
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="todo in todos">
<label>
<span>
{{ todo.text }}
</span>
</label>
</li>
</ol>
</div>