Vue - Accessing Attributes From One Component to Another - vue.js

New to vue and struggling to understand how data is passed back and forth between components. I'm aware of props and emit on the parent/child, child/parent, but I can't quite understand how they work in my case. I have two components: a parent component called "Letters" and a child called "ClaimantSearch". Claimant search return data about a person based on a call to a flask backend:
<div>
<b-form #submit="onSubmit" class="w-100">
<b-form-group id="form-title-group"
label="Claim Number:"
label-for="form-claim-number-input">
<b-form-input id="form-claim-number-input"
type="text"
v-model="claimNumberForm.claimNumber"
required
placeholder="Enter claim number">
</b-form-input>
<button
type="button"
class="btn btn-warning btn-sm"
#click="getClaimant(claimNumber)">
Get Claimant
</button>
</b-form-group>
</b-form>
<span v-if="claimant">
<p> {{ claimant.name }} </p>
<p> {{ claimant.address1 }} </p>
<p> {{ claimant.address2 }} </p>
<p> {{ claimant.city }}, {{ claimant.state }} </p>
<p> {{ claimant.zip }} </p>
</span>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
claimant: '',
claimNumberForm: {
claimNumber: '',
},
};
},
methods: {
getClaimant(number) {
const path = `http://localhost:5000/claimant/${number}`;
axios.get(path)
.then((res) => {
this.claimant = res.data.claimant;
})
.catch((error) => {
// eslint-disable-next-line
console.error(error);
});
},
onSubmit(evt) {
evt.preventDefault();
this.getClaimant(this.claimNumberForm.claimNumber);
},
},
};
</script>
I then have a Letters parent component:
<template>
<div>
<claimant></claimant>
</div>
</template>
<script>
// import axios from 'axios';
import ClaimantSearch from './ClaimantSearch.vue';
export default {
data() {
return {
claimant: '',
claimNumberForm: {
claimNumber: '',
},
};
},
components: {
claimant: ClaimantSearch,
},
methods: {
},
};
</script>
What I'd like to be able to do is access {{claimant}} outside of the <claimant> tag, if that makes sense. So inside Letters I'd like to do something like:
<template>
<div>
<div>
<claimant></claimant>
</div>
<div>
Dear Mr. {{claimant.name}},
Please get bent. Sincerly, crappy insurance company.
</div>
</div>
</template>

I can't remember exactly where I found this link, but there's an excellent post on medium, with code samples, that discusses all the state management patterns in vue starting with props and events, eventbus, simple store and then vuex.
https://medium.com/fullstackio/managing-state-in-vue-js-23a0352b1c87

Related

VUE3 use different v-for for the same component

I have a JobComponent.vue component where I fetch data from a VUEX Store. This component is used on two separate pages, first page Home.vue and second page AllJobs.vue.
In AllJobs.vue I used JobComponent.vue and everything is works fine, it's rendering all the jobs, but, here comes the problem...
In Home.vue I want to render only the last 5 jobs, so in store I make a getter that slice me only the latest 5 jobs.
How can I use this latestJobs from getters on the same component?
When I import the component in Home.vue page I can't use another v-for direct on the component...
here you can see my project structure and files
Home.vue
<template>
<div class="cards-container">
<JobComponent />
</div>
</template>
JobComponent.vue
<template>
<div v-for="job in allJobs" :key="job.id" class="card">
<div class="position">{{ job.position }}</div>
<div class="department">{{ job.department }}</div>
<div class="location">
<span class="material-symbols-outlined">location_on</span>
{{ job.location }}
</div>
<span class="material-symbols-outlined right-arrow">arrow_right_alt</span>
<span #click="deleteJob(job.id)" class="material-symbols-outlined right-arrow">delete</span>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
methods: {
...mapActions(['fetchJobs', 'deleteJob']),
},
computed: mapGetters(['allJobs']),
created() {
this.fetchJobs();
}
}
</script>
store.js (vuex)
const getters = {
allJobs: (state) => state.jobs,
latestJobs: (state) => {
const response = state.jobs.slice(0, 5);
return response;
}
};
Your component should be as independent as possible from the store. It's role is to display what ever is provided so it could be reused as you want, using props :
JobComponent.vue
<template>
<div class="card">
<div class="position">{{ position }}</div>
<div class="department">{{ department }}</div>
<div class="location">
<span class="material-symbols-outlined">location_on</span>
{{ location }}
</div>
<span class="material-symbols-outlined right-arrow">arrow_right_alt</span>
<span #click="$emit('deleteJob', id)" class="material-symbols-outlined right-arrow">delete</span>
</div>
</template>
<script>
export default {
props: {
id: string,
position: string,
department: string,
location: string
}
}
</script>
In this component you only display the provided data, and leave the responsibility of the parent component to choose how many components to display.
Home.vue
<template>
<div class="cards-container">
<JobComponent v-for="job in jobs" :key="job.id" :id="job.id" :position="job.position" :department="job.department" :location="job.location" #delete-job="deleteJob" />
</div>
</template>
<script>
export default {
created() {
this.$store.dispatch('fetchJobs')
},
computed: {
jobs() {
return this.$store.getters['latestJobs'] // Or allJobs, just make sure your getter returns an array even if no jobs are loaded yet.
}
},
methods: {
deleteJob() {
// Your logic for job delete
}
}
}
</script>

Problem with Recursive Components in Vue.js

I'm a beginner in Vue.js and I'm getting stuck on using recursive components.
When I try to recall the GroupFilter component in the GroupFilter component, the test message "New filter group" is displayed fine, but not the new GroupFilter component.
I have tried using an import and using the name given to the component, but in both cases it does not work.
Does anyone know how to make this work ? Thank you in advance for your help !
<template>
<div class="groupFilter">
<p>OR</p>
<div class='groupFilterForms'>
<div class="selectButton">
<button type="button" class="btn btn-primary" v-on:click="addNewFilter">+ Add Filter</button>
<button type="button" class="btn btn-success" v-on:click="addNewGroupFliter">+ Add Filter Group</button>
</div>
<div v-for="(child, index) in this.$store.state.storeData[this.index].$all" :index="index" :key="index">
<div v-if="child.condition">
<FilterTemplate :index='index'/>
</div>
<div v-else-if="child.$all">
<h3>New filter group</h3>
<GroupFilter/>
</div>
</div>
</div>
</div>
</template>
<script>
import FilterTemplate from'./FilterTemplate';
import GroupFilter from './GroupFilterTemplate'
export default {
name: 'GroupFilter',
components: {
FilterTemplate,
GroupFilter
},
data: function (){
return {
storeData: [],
props: this.index,
toto: Array
}
},
props: ['index'],
mounted: function(){
this.storeData = this.$store.state.storeData
},
methods: {
addNewFilter(){
console.log("new filter TEST")
this.storeData[this.props].$all.push(
{condition:{
"attr":"group_id",
"ope":"eq",
"value":106
}}
);
this.$store.commit('setStoreData', this.storeData);
console.log(this.storeData[index])
},
addNewGroupFliter(){
console.log("new group filter TEST")
this.storeData[this.props].$all.push({
$all:[
{condition:{
"attr":"group_id",
"ope":"eq",
"value":106
}}
]
})
this.$store.commit('setStoreData', this.storeData);
}
}
}
</script>

Missing required prop in Vue.js

I am new to vue.js so maybe i'm missing something obvious. I have created 2 components Content.vue & ViewMore.vue. I am passing property "genre" which is inside array "animesByGenre" in Content.vue to ViewMore.vue but somehow it is not working.
Here is the Content.vue component:
<div v-for="(animesAndGenre, index) in animesByGenres" :key="index"
id="row1" class="container">
<h5>
{{animesAndGenre.genre.toUpperCase()}}
<button class="viewMore" v-bind:genre="animesAndGenre.genre"><router-link :to="{name: 'viewmore'}">view more</router-link></button>
</h5>
<vs-row vs-justify="center" class="row">
<vs-col v-for="(anime, i) in animesAndGenre.animes" :key="i"
vs-type="flex" vs-justify="center"
vs-align="center" vs-w="2" class="animeCard">
<vs-card actionable class="cardx">
<div slot="header" class="cardTitle">
<strong>
{{anime.attributes.canonicalTitle}}
</strong>
</div>
<div slot="media">
<img :src="anime.attributes.posterImage.medium">
</div>
<div>
<span>Rating: {{anime.attributes.averageRating}}</span>
</div>
<div slot="footer">
<vs-row vs-justify="center">
<vs-button #click="addAnimeToWatchlist(anime)"
color="primary" vs-type="gradient" >
Add to Watchlist
</vs-button>
</vs-row>
</div>
</vs-card>
</vs-col>
</vs-row>
</div>
<script>
import ViewMore from './ViewMore.vue';
import axios from 'axios'
export default {
name: 'Content',
components: {
'ViewMore': ViewMore,
},
data () {
return {
nextButton: false,
prevButton: false,
viewMoreButton: false,
results: '',
animes: '',
genres: ['adventure', 'action', 'thriller', 'mystery', 'horror'],
animesByGenres: []
}
},
created() {
this.getAnimes();
// this.getRowAnime();
this.genres.forEach( (genre) => {
this.getAnimeByGenres(genre);
});
},
}
</script>
Here is the ViewMore.vue component (i'm just trying to log genre for now):
<template>
<div>
</div>
</template>
<script>
import axios from 'axios'
export default {
props: {
genre: {
type: String,
required: true,
},
},
data() {
return {
allAnimes: '',
}
},
created() {
console.log(this.genre);
}
}
</script>
Passing props to routes doesn't work like that. Right now, all this code is doing is applying the genre prop to the button itself, not to the route it's going to. You'll need to add the genre to the URL as a param (/viewmore/:genre/), or as a part of the query (/viewmore?genre=...). See this page for how that works

Send data from one component to another in vue

Hi I'm trying to send data from one component to another but not sure how to approach it.
I've got one component that loops through an array of items and displays them. Then I have another component that contains a form/input and this should submit the data to the array in the other component.
I'm not sure on what I should be doing to send the date to the other component any help would be great.
Component to loop through items
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Name</p>
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in entries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry />
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
}
}
</script>
Component for adding / sending data
<template>
<div
class="entry-add"
v-bind:class="{ 'entry-add--open': addEntryIsOpen }">
<input
type="text"
name="addEntry"
#keyup.enter="addEntries"
v-model="newEntries">
</input>
<button #click="addEntries">Add Entries</button>
<div
class="entry-add__btn"
v-on:click="openAddEntry">
<span>+</span>
</div>
</div>
</template>
<script>
export default {
name: 'add-entry',
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push(this.newEntries);
this.newEntries = '';
},
openAddEntry() {
this.addEntryIsOpen = !this.addEntryIsOpen;
}
}
}
</script>
Sync the property between the 2:
<add-entry :entries.sync="entries"/>
Add it as a prop to the add-entry component:
props: ['entries']
Then do a shallow merge of the 2 and emit it back to the parent:
this.$emit('entries:update', [].concat(this.entries, this.newEntries))
(This was a comment but became to big :D)
Is there a way to pass in the key of name? The entry gets added but doesn't display because im looping and outputting {{ entry.name }}
That's happening probably because when you pass "complex objects" through parameters, the embed objects/collections are being seen as observable objects, even if you sync the properties, when the component is mounted, only loads first level data, in your case, the objects inside the array, this is performance friendly but sometimes a bit annoying, you have two options, the first one is to declare a computed property which returns the property passed from the parent controller, or secondly (dirty and ugly but works) is to JSON.stringify the collection passed and then JSON.parse to convert it back to an object without the observable properties.
Hope this helps you in any way.
Cheers.
So with help from #Ohgodwhy I managed to get it working. I'm not sure if it's the right way but it does seem to work without errors. Please add a better solution if there is one and I'll mark that as the answer.
I follow what Ohmygod said but the this.$emit('entries:update', [].concat(this.entries, this.newEntries)) didn't work. Well I never even need to add it.
This is my add-entry.vue component
<template>
<div
class="add-entry"
v-bind:class="{ 'add-entry--open': addEntryIsOpen }">
<input
class="add-entry__input"
type="text"
name="addEntry"
placeholder="Add Entry"
#keyup.enter="addEntries"
v-model="newEntries"
/>
<button
class="add-entry__btn"
#click="addEntries">Add</button>
</div>
</template>
<script>
export default {
name: 'add-entry',
props: ['entries'],
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push({name:this.newEntries});
this.newEntries = '';
}
}
}
</script>
And my list-entries.vue component
<template>
<div class="container-flex">
<div class="wrapper">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Competition Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredEntries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry :entries.sync="entries"/>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
import pickWinner from '#/components/pick-winner.vue'
export default {
name: 'entry-list',
components: {
addEntry,
pickWinner
},
data: function() {
return {
search: '',
entries: [
{
name: 'Geoff'
},
{
name: 'Stu'
},
{
name: 'Craig'
},
{
name: 'Mark'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredEntries() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
</script>

Access infromation of grandparent component from grandchild component in vue.js

I have a parent component in Vue called RecipeView, and it is an inline-component. inside it, i have these components:
comments. and inside comments, i have comment and NewCommentForm, has it shows in the picture below.
I am passing in the RecipeView component the id as a prop, and would like to access it in the NewCommentForm component in order to set an endpoint that i will post to and save the comment.
This is the RecipeView component:
<recipe-view :id="{{$recipe->id}}">
<comments :data="{{$recipe->comment}}"#added="commentsCount++"></comments>
</recipe-view>
and the script for it is this:
<script>
import Comments from '../components/Comments.vue';
export default {
props: ['initialCommentsCount','id'],
components: {Comments},
data(){
return {
commentsCount: this.initialCommentsCount,
recipe_id:this.id
};
}
}
</script>
The comments component looks like this:
<template>
<div>
<div v-for="comment in items">
<comment :data="comment"></comment>
</div>
<new-comment-form :endpoint="'/comments/**Here should go the id from parent RecipeView component**'" #created="add"></new-comment-form>
</div>
</template>
<script>
import Comment from './Comment.vue';
import NewCommentForm from './NewCommentForm.vue';
export default {
props: ['data'],
components: {Comment, NewCommentForm},
data() {
return {
items: this.data,
endpoint: ''
}
},
methods: {
add(comment) {
this.items.push(comment);
this.$emit('added');
}
}
}
</script>
and this is the NewCommentForm component:
<template>
<div>
<div class="field">
<p class="control">
<input class="input"
type = "text"
name="name"
placeholder="What is your name?"
required
v-model="name">
</p>
</div>
<div class="field">
<p class="control">
<textarea class="textarea"
name="body"
placeholder="Have your say here..."
required
v-model="body">
</textarea>
</p>
</div>
<button type="submit"
#click="addComment"
class="button is-medium is-success">send</button>
</div>
</template>
<script>
export default {
props:['endpoint'],
data(){
return {
body:'',
name:'',
}
},
methods:{
addComment(){
axios.post(this.endpoint, {
body:this.body,
name: this.name
}).then(({data}) => {
this.body = '';
this.name = '';
this.$emit('created', data);
});
}
}
}
</script>
Thanks for the help.