The problem of data transfer between two child components - javascript

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.

Related

Why computed property works on preserving the reactivity but attributes in data() {} does not

I'm following this tutorial, where they point out a small bug with the checkbox status of forgetting the state of the ticked/unticked which can be solved by using the computed property.
I would like to ask, even if the attribute isDone in Data (ToDoItem.vue) has been changed to true (by ticking the checkbox), why the box is still unticked after clicking edit then cancel, and why computed property could solve this bug.
Below are parts of the scripts.
ToDoItem.vue
<template>
<div class="stack-small" v-if="!isEditing">
<div class="custom-checkbox">
<input
type="checkbox"
class="checkbox"
:id="id"
:checked="isDone"
#change="$emit('checkbox-changed')"
/>
<label :for="id" class="checkbox-label">{{ label }}</label>
</div>
<div class="btn-group">
<button
type="button"
class="btn"
ref="editButton"
#click="toggleToItemEditForm"
>
Edit <span class="visually-hidden">{{ label }}</span>
</button>
<button type="button" class="btn btn__danger" #click="deleteToDo">
Delete <span class="visually-hidden">{{ label }}</span>
</button>
</div>
</div>
<to-do-item-edit-form
v-else
:id="id"
:label="label"
#item-edited="itemEdited"
#edit-cancelled="editCancelled"
></to-do-item-edit-form>
</template>
<script>
import ToDoItemEditForm from "./ToDoItemEditForm";
export default {
components: {
ToDoItemEditForm,
},
props: {
label: { required: true, type: String },
done: { default: false, type: Boolean },
id: { required: true, type: String },
},
data() {
return {
isEditing: false,
isDone: this.done, // after deleting this line and use
//computed: {} below, the bug is solved.
};
},
// computed: {
// isDone() {
// return this.done;
// },
// },
};
</script>
ToDoItem.vue
<template>
<div id="app">
<h1>To-Do List</h1>
<to-do-form #todo-added="addToDo"></to-do-form>
<h2 id="list-summary" ref="listSummary" tabindex="-1"> {{ listSummary }} </h2>
<ul aria-labelledby="list-summary" class="stack-large">
<li v-for="item in ToDoItems" :key="item.id">
<to-do-item
:label="item.label"
:done="item.done"
:id="item.id"
#checkbox-changed="updateDoneStatus(item.id)"
#item-deleted="deleteToDo(item.id)"
#item-edited="editToDo(item.id, $event)"
>
</to-do-item>
</li>
</ul>
</div>
</template>
<script>
import ToDoItem from "./components/ToDoItem.vue";
import ToDoForm from "./components/ToDoForm.vue";
import uniqueId from "lodash.uniqueid";
export default {
name: "app",
components: {
ToDoItem,
ToDoForm,
},
data() {
return {
ToDoItems: [],
};
},
methods: {
updateDoneStatus(toDoId) {
const toDoToUpdate = this.ToDoItems.find((item) => item.id === toDoId);
toDoToUpdate.done = !toDoToUpdate.done;
console.dir(toDoToUpdate.done)
},
};
</script>
I'm not an expert in vue, but I believe that the this.done being assigned to isDone: is only done once in data(), and it wouldn't be done if the props change (the value of isDone in data() won't change when the prop done changes). While in computed, isDone will watch the done prop value, and if that prop value changes, the computed will be notified and thus changes the isDone data.

how to pass data from api as prop in vuejs

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" />

Vue.js update contents in div dynamically

I am attempting to do a SPA using Vue.js but unfortunately I know almost nothing about it, I followed a tutorial and got something up and running. This should hopefully be relatively simple!
I'm trying to create a simple page that:
Does a REST API call and pulls some JSON
A list with links of a particular field in the list of results is displayed on the left side of the screen
(I've managed until here)
Now I would like to be able to click on one of the links and see on the right side of the screen the value of another field for the same record.
For instance, suppose my JSON is:
{
"jokes":{
[
"setup":"setup1",
"punchline":"punchline1"
],
[
"setup":"setup2",
"punchline":"punchline2"
],
[
"setup":"setup3",
"punchline":"punchline3"
]
}
}
So in my screen I would see:
setup1
setup2
setup3
So if I click in setup1 I see punchline1, setup2 displays punchline2 and so on.
Here is my code - I'm basically trying to display the punchline in the moduleinfo div. I realise the current solution does not work. I've been searching but can't find any similar examples. Any pointers would be greatly appreciated.
<template>
<div class="home">
<div class="module-list">
<input type="text" v-model.trim="search" placeholder="Search"/>
<div>
<ul>
<li class="modules" v-for="value in modulesList" :key="value.id">
{{ value.setup }}
</li>
</ul>
</div>
</div>
<div class="moduleinfo">
<h2>Module info</h2>
<!-- <p>{{ value.punchline }}</p> -->
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Home',
data: function(){
return {
jokes: [],
search : ""
}
},
mounted() {
this.getModules();
},
methods: {
getModules() {
var self = this
const options = {
method: 'GET',
url: 'https://dad-jokes.p.rapidapi.com/joke/search',
params: {term: 'car'},
headers: {
'x-rapidapi-key': '...',
'x-rapidapi-host': 'dad-jokes.p.rapidapi.com'
}
};
axios.request(options)
.then(response => {
self.jokes = response.data;
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
}
},
computed: {
modulesList: function () {
var jokes = this.jokes.body;
var search = this.search;
if (search){
jokes = jokes.filter(function(value){
if(value.setup.toLowerCase().includes(search.toLowerCase())) {
return jokes;
}
})
}
return jokes;
}
},
};
</script>
Thanks!
I was building a sample Single File Component in my Vue 2 CLI app, and when I came back to post it, Ryoko had already answered the question with the same approach that I recommend, adding a new property to track showing the punchline.
Since I already built it, I figured that I might as well post my component, which does change the layout, using a table instead of a list, but the functionality works.
<template>
<div class="joke-list">
<div class="row">
<div class="col-md-6">
<table class="table table-bordered">
<thead>
<tr>
<th>SETUP</th>
<th>PUNCHLINE</th>
</tr>
</thead>
<tbody>
<tr v-for="(joke, index) in jokes" :key="index">
<td>
{{ joke.setup }}
</td>
<td>
<span v-if="joke.showPunchline">{{ joke.punchline }}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
jokes: [
{
setup: "setup1",
punchline: "punchline1"
},
{
setup: "setup2",
punchline: "punchline2"
},
{
setup: "setup3",
punchline: "punchline3"
}
]
}
},
methods: {
getPunchline(index) {
this.jokes[index].showPunchline = true;
},
addPropertyToJokes() {
// New property must be reactive
this.jokes.forEach( joke => this.$set(joke, 'showPunchline', false) );
}
},
mounted() {
this.addPropertyToJokes();
}
}
</script>
You can add a new property inside the data object and then make a new method to set it accordingly when you click the <a> tag. Have a look at the code below, it was a copy of your current solution, edited & simplified to show the addition that I made to make it easier for you to find it.
The select method will insert the object of the clicked joke to the selectedJoke so you can render it below the Module Info.
Because it's defaults to null, and it might be null or undefined, you have to add v-if to the attribute to check wether there is a value or not so you don't get error on the console.
<template>
<div class="home">
<div class="module-list">
<input type="text" v-model.trim="search" placeholder="Search"/>
<div>
<ul>
<li class="modules" v-for="value in modulesList" :key="value.id">
{{ value.setup }}
</li>
</ul>
</div>
</div>
<div class="moduleinfo">
<h2>Module info</h2>
<p v-if="selectedJoke">{{ selectedJoke.punchline }}</p>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Home',
data: function(){
return {
jokes: [],
search : "",
selectedJoke: null,
}
},
methods: {
select(joke) {
this.selectedJoke = joke;
},
},
};
</script>

Passing Props Child -> Parent -> Other Child

I'm trying to create a basic form that, once input is submitted, sends data to the parent and is then rendered in a list as a card. Documentation has been pointing me towards using the Event Bus, but it all seems a little too over engineered for such a simple task. Not to mention its not working xD. Am I on the right track here? or missing the whole idea?
The Data seems to be updating on submit, but I'm not seeing a card render. I'm also seeing the follow error,
Property or method "initiativeList" is not defined on the instance but referenced during render.
I do, however, notice a particularly odd change in the render. Instead of a child being rendered in EncounterList.js the child's attributes are merging into the parent .
Any help is greatly appreciated.
EncounterDashboard.js
<template>
<div>
<NewCharacterForm #add-char="addChar" />
<EncounterList v-bind="encounterList" #add-char="addChar" />
</div>
</template>
<script>
import Character from "../classes/Encounter";
import NewCharacterForm from "./NewCharacterForm/NewCharacterForm.vue";
import EncounterList from "./EncounterList/EncounterList";
import EventBus from "./EventBus.js";
export default {
name: "EncounterDashboard",
components: { NewCharacterForm, EncounterList },
data() {
return {
newChar: {},
encounterList: []
};
},
methods: {
addChar(newChar) {
this.newChar = newChar;
this.encounterList.push(newChar);
EventBus.$emit("add-to-list", this.encounterList);
}
}
};
</script>
NewCharacterForm.js
<template>
<div class="new-char-wrapper">
<form class="char-form" ref="form" v-on:submit.prevent="handleSubmit">
<NewCharInput class="name-input" label="NAME" name="name" v-model="name" />
<div class="stat-wrapper">
<NewCharInput
class="init-input"
label="INITIATIVE"
name="initiative"
v-model="initiative"
type="number"
/>
<NewCharInput class="hp-input" label="HP" name="hp" v-model="hp" type="number" />
</div>
<div class="submit-row">
<button class="submit">SUBMIT</button>
</div>
</form>
</div>
</template>
<script>
import NewCharInput from "./NewCharInput";
import Character from "../../classes/Character";
import { uuid } from "vue-uuid";
export default {
name: "NewCharacterForm",
components: { NewCharInput },
data() {
return {
name: "",
initiative: "",
hp: 0
};
},
props: ["addChar"],
methods: {
handleSubmit() {
const charName = this.$refs.form.name.value;
const charInitiative = this.$refs.form.initiative.value;
const charHp = this.$refs.form.hp.value;
const charId = this.$uuid.v4();
const newChar = new Character(charName, charInitiative, charId, charHp);
this.$emit("add-char", newChar);
}
}
};
</script>
EncounterList.js
<template>
<div class="encounter-list">
<div class="header-row">
<h2 class="header col-init">INIT</h2>
<h2 class="header col-name">NAME</h2>
<h2 class="header col-hp">HP</h2>
</div>
<EncounterCard
v-for="character in initiativeList"
v-bind:key="character.id"
v-bind:hp="character.hp"
v-bind:name="character.name"
v-bind:initiative="character.initiative"
/>
</div>
</template>
<script>
import EncounterCard from "../EncounterCard/EncounterCard";
import EventBus from "../EventBus";
export default {
name: "EncounterList",
components: { EncounterCard },
data() {
return {
data: {
initiativeList: []
}
};
},
methods: {
populateList(charList) {
this.initiativeList = charList;
}
},
mounted() {
EventBus.$on("add-to-list", charList => {
this.populateList(charList);
});
}
};
</script>
EncounterCard.js
<template>
<div class="encounter-card-wrapper">
<h1 class="char-init">{{character.initiative}}</h1>
<h1 class="char-name">{{character.name}}</h1>
<h1 class="char-hp">{{character.hp}}</h1>
</div>
</template>
<script>
export default {
name: "EncounterCard",
props: ["character"]
};
</script>
data() {
return {
data: { //Is this what you're trying to do?
initiativeList: []
}
};
},
If the data attribute is intended, "initiativeList" should be changed to "data.initiativeList".
<EncounterCard
v-for="character in data.initiativeList"
v-bind:key="character.id"
v-bind:hp="character.hp"
v-bind:name="character.name"
v-bind:initiative="character.initiative"
/>
and
populateList(charList) {
this.data.initiativeList = charList;
}

How do I get rid of problem with values in input?

I have post.title and post.body in value, and I need that after changing the values in the text inputs, it is saved in data so that I can use these new values later for the user to write (PUT request) on the API. How can I achieve this?
Here's my code -
<template>
<div id="app">
<input type="text" v-model="createTitle" />
<input type="text" v-model="createBody" />
<button #click="addPost()">AddPost</button>
<ul>
<li v-for="(post, index) of posts">
<p>{{ post.title }}</p>
<p>{{ post.body }}</p>
<button #click="deleteData(index, post.id)">Delete</button>
<button #click="visiblePostID = post.id">
Изменить
</button>
<transition v-if="visiblePostID === post.id">
<p><input :value="post.title"><br><input :value="post.body">
<button type="button" #click="changePost(post.id, post.title, post.body)">Применить</button></p>
</transition>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'app',
data() {
return {
posts: [],
createTitle: '',
createBody: '',
visiblePostID: '',
}
},
changePost(id, title, body) {
axios.put('http://jsonplaceholder.typicode.com/posts/' + id, {
title: title,
body: body
})
}
}
</script>
For two-way data binding you should use v-model. Read here.
<transition v-if="visiblePostID === post.id">
<p>
<input v-model="post.title">
<br>
<input v-model="post.body">
<button type="button" #click="changePost(post.id, post.title, post.body)">Применить</button>
</p>
</transition>
To add to #Riddhi's answer, you could use v-model on those inputs with temporary variables so that the model is not modified until the PUT-request is confirmed successful:
Add temporary data properties to hold the <input> values from the template:
// template
<transition v-if="visiblePostID === post.id">
<input v-model="tmpTitle" />
<input v-model="tmpBody" />
</transition>
// script
data() {
return {
tmpTitle: '',
tmpBody: ''
}
}
Replace the edit-button's handler with a method (named editPost()), and pass to the method the current post's ID, title, and body, which will be stored in the temporary data properties declared above:
// template
<button #click="editPost(post.id, post.title, post.body)">
Изменить
</button>
// script
methods: {
editPost(id, title, body) {
this.tmpTitle = title;
this.tmpBody = body;
this.visiblePostID = id;
}
}
Update changePost() to also take the current post, which will be updated with the temporary data properties once the PUT request is successful.
// template
<button type="button" #click="changePost(post, post.id, tmpTitle, tmpBody)">
Применить
</button>
// script
methods: {
async changePost(post, id, title, body) {
const { status } = await axios.put("https://jsonplaceholder.typicode.com/posts/" + id, { title: title, body: body });
if (status === 200 /* HTTP OK */) {
post.title = title;
post.body = body;
}
}
}
demo

Categories