Vue.js infinite loop on component re-render [closed] - javascript

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I'm trying to build tables based off a few selected properties from a previous component:
I'm rendering a component called 'branch-comparison' to compare XML files and their properties and values. This component takes in two props:
selectedEnvs: An array of objects with a name and object
commonFiles: An array of files with a name and object
I'm using vue-tables-2 to build these tables. At the top of the template it runs a function called getProps() to generate a set of all possible properties from each file. I've hard coded 0 because currently I'm only letting the user choose 1 file at a time. It then goes through each file (only 1) and gets data for the main table and the comparison tables. They are virtually the same function (getHeadData and getTableData) but I've seperated them for now for further customization. The code is not that important for actually generating the tables, however something inside of them is causing my code to go in an infinite loop.
On the initial render of the component, there is never an infinite loop. Everything runs through, and doesn't break at all and works wonderfully. Once however the component has been rendered, and I make a change to the props, or even simply save the file in the editor and vue-cli hot reloads it, it goes into and infinite loop. All the data still get's generate fine and the component does as it's supposed to. But it loops through 101 times no matter what.
Things I've looked into:
Changing the data: I fully understand a component re renders on data change... however I don't believe I am changing any reactive data in any method call. I'm simply declaring it locally inside the function and returning it to that temporary variable. Also if this was the case, I believe it would go into an infinite loop on the initial component load, but this is not the case. It goes into the infinite loop only on a refresh or prop change.
Mutating the Vuex state: I looked into this but I am never changing the state of anything. I am simply accessing it in the getTableData and getHeadData methods. I then thought, perhaps assigning a variable to point to this state object is causing it to re render based on something accessing the state, so I tried instead of
this.$store.state.branchesToCompare[branchIdx].obj[env.name].app_config[this.commonFiles[fileIdx]].forEach(envProp
=> {
to use
var x = JSON.parse(JSON.stringify(this.$store.state.branchesToCompare[branchIdx].obj[env.name].app_config[this.commonFiles[fileIdx]])
then
x.forEach(envProp =>
but this still does not work.
If I comment out the code that calls getHeadData() and getTableData() it loops through the appropriate amount of times.
Here is the code.. I am still new to Vue so any more general suggestions I am more than open to:
<template>
<div id="BranchComparison">
<div :set="info = getProps(0)">
<div class="file" v-for="(file, fileIdx) in commonFiles" :key="(file, fileIdx)">
<h3>{{ file }} </h3>
<b-row :set="mainTable = getHeadData(fileIdx, info.props, info.columns)">
<b-col class="mainBranch">
<h5 class="fileName"> {{ $store.state.branchSelection.split('.').slice(0, -1).join('.') }} <span style="font-size: 14px;">vs </span> </h5>
<v-client-table
:data="mainTable.data"
:columns="mainTable.columns"
:options="mainTableOptions"
size="small"
></v-client-table>
</b-col>
<b-col class="compareBranch" v-for="(branch, branchIdx) in $store.state.branchesToCompare" :key="(branch, branchIdx)">
<h5> {{ branch.name.split('.').slice(0, -1).join('.') }} </h5>
<v-client-table
:set="temp = getTableData(fileIdx, branchIdx, info.props, info.columns, mainTable)"
:data="temp.data"
:columns="temp.columns"
:options="temp.options"
size="small"
></v-client-table>
</b-col>
</b-row>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['selectedEnvs', 'commonFiles'],
data(){
return{
mainTableOptions:{
filterable: false,
filterByColumn: false,
perPage: 200,
pagination: {
show: false,
dropdown: false
},
sortable: [''],
resizableColumns: false,
},
}
},
methods: {
getTableData(fileIdx, branchIdx, props, columns, mainTable){
var data = []
var compareTableOptions = {
filterable: false,
perPage: 200,
pagination: {
show: false,
},
sortable: [''],
hiddenColumns: ['Property'],
resizableColumns: false,
cellClasses: {}
}
props.forEach(prop => {
var temp = { Property: prop }
this.selectedEnvs.forEach(env => {
var found = false;
this.$store.state.branchesToCompare[branchIdx].obj[env.name].app_config[this.commonFiles[fileIdx]].forEach(envProp => {
if(envProp){
if (prop == envProp["#name"]) {
compareTableOptions.cellClasses[env.name] = []
compareTableOptions.cellClasses[env.name].push({
class: 'same',
condition: row => {
try{
return row[env.name] == mainTable.data[i][env.name]
} catch{
console.log('This is a different problem ')
}
}
})
found = true;
temp[env.name] = envProp["#value"]
}
}
});
if (!found){
temp[env.name] = 'Not found'
}
})
data.push(temp)
});
return {
columns: columns,
data: data,
options: compareTableOptions
}
},
getHeadData(fileIdx, props, columns){
var data = []
props.forEach(prop => {
var temp = { Property: prop }
this.selectedEnvs.forEach(env => {
var found = false;
this.$store.state.jsonObject[env.name].app_config[this.commonFiles[fileIdx]].forEach(envProp => {
if(envProp){
if (prop == envProp["#name"]) {
found = true;
temp[env.name] = envProp["#value"]
}
}
});
if (!found){
temp[env.name] = 'Not found'
}
})
data.push(temp)
});
return {
columns: columns,
data: data
}
},
getProps(fileIdx){
if(this.commonFiles.length == 0) return
var columns = ['Property']
var props = new Set()
this.selectedEnvs.forEach((env, idx) => {
columns.push(env.name)
this.$store.state.branchesToCompare.forEach(branch => {
branch.obj[env.name].app_config[this.commonFiles[fileIdx]].forEach(prop => {
if(prop){
props.add(prop["#name"])
}
})
});
this.$store.state.jsonObject[env.name].app_config[this.commonFiles[fileIdx]].forEach(prop => {
if(prop){
props.add(prop["#name"]);
}
});
});
var ret = { props: props, columns: columns }
return ret;
}
}
}
</script>

I've solved it. The code above is actually fine. Shortly before I posted the code, I was using a computed property in the v-for AND in the getHeadData(), what I think was happening was it was a nested computed property, and on the inner loop it recomputed it and then tried the outer loop again, and so forth. I'm still puzzled why it work on the initial render, but oh well. It is working now.

Related

Timing issue with Algolia and VueJs when customising the refinement list

I am using Algolia to build up a refinement list of filters, and I have customized the algolia instance to display facets with no matches: Algolia docs on displaying facets with no matches
The issue I am having is that in the mounted function in VueJs Algolia is building up the refinement list, and then doing some magic to build the Dom. I am having an issue where I am trying to append a CSS class to these elements in the same mounted function but the NodeList I am working on is constantly returning [] empty.
My solution is completely working when typing, and when using the refinement list, but its not working on page reload as the timing of things is out of sync.
mounted() {
this.searchClient
.searchForFacetValues([
{
indexName: this.indexName,
params: {
facetName: this.brandAttribute,
facetQuery: '',
maxFacetHits: this.brandLimit,
},
},
])
.then(([{ facetHits }]) => {
this.initialFacets.push(
...facetHits.map(facet => ({
...facet,
label: facet.value,
value: facet.value,
isRefined: false,
count: 0,
}))
);
});
console.log(this.$el.querySelectorAll(".ais-RefinementList-list .ais-RefinementList-count")); //Returns empty
setTimeout(() => {
console.log(this.$el.querySelectorAll(".ais-RefinementList-list .ais-RefinementList-count")); //Is populated
}, 5000);
},
I have set a timeout to prove this is a timing issue. Inside my timeout The nodelist is returning as expected, and I can add my CSS class, but how long do I wait? I don't believe this is a correct solution
I have tried all VueJs lifecycle hooks and in all the NodeList is returning null.
What I need is:
After the page has completly loaded, loop over my nodeList and Add CSS. The way I am doing this is:
document.querySelectorAll(".ais-RefinementList-list .ais-RefinementList-count").forEach(
function(x) {
if(x.innerHTML == 0) {
x.parentElement.style.backgroundColor = "red"
} else {
x.parentElement.style.backgroundColor = "white"
}
}
);
Working Solution:
Page Refresh, timing issue:
Putting code in timeout:
setTimeout(() => {
this.$el.querySelectorAll(".ais-RefinementList-list .ais-RefinementList-count").forEach(
function(x) {
if(x.innerHTML == 0) {
x.parentElement.style.backgroundColor = "red"
} else {
x.parentElement.style.backgroundColor = "white"
}
}
);
}, 5000);
Gives this solution after 5 seconds: Working:
I was able to overcome this with the solution of customizing the UI: Algolia docs, customizing the UI
I completly overwrote my Refinement list, and conditionally added the needed classes using the :style binding directive.
<ais-refinement-list
attribute="ParentPageTitle"
:transformItems="transformItems"
>
<label
slot="item"
slot-scope="{ item }"
class="ais-RefinementList-label"
:style="[item.count ? {'background-color' : 'white'} : {'background-color' : 'red'}]"
>
<input type="checkbox" class="ais-RefinementList-checkbox">
<span class="ais-RefinementList-labelText">{{item.value}}</span>
</label>
</ais-refinement-list>
CSS is now able to correctly style the component on runtime, and the timing issue has been resolved

For sibling communication between many identical components, how should I store the data in the lowest-common ancestor?

Background:
I'm a Python/Vue developer; I've been using Vue since 2016.
I have a client who runs a weight loss / meal planning business: clients pay her to prepare weekly single-page PDF menus that tell them (the clients) exactly what to eat for breakfast, lunch, and dinner of every day of the week. (image of an example menu)
Each meal is shown as a list of ingredients.
Right now she's preparing these menus in Excel, and she hired me to reproduce and extend the functionality of what she has in Excel, but in a Python/Vue app.
The app I'm building for her has many "pages" ("top-level" components) to allow her to add/modify/delete objects like clients, ingredients, and recipes (image), but the most complicated part of the UI is the component in which she can define the meals for every meal of every day of the week (image). That component is named WeeklyMenu.vue.
WeeklyMenu.vue itself contains seven DailyMenu.vue children, one for each day of the week (Monday, Tuesday, etc.). (image)
Each DailyMenu.vue component itself contains four Meal.vue components, one for each of four meal types: Breakfast, Lunch, Dinner, and Snacks. (image)
Important: At the moment, the DailyMenu.vue and Meal.vue components themselves contain their data rather than accessing it from the Vuex store.
For example, the list of ingredients for each meal is contained within the Meal.vue component as a mealIngredients variable within the component's data attribute. (image)
Side-note: This means that there are lots of HTTP requests being sent to the back-end when the page loads as all of the meals are requesting their own data, rather than a single request being sent via a Vuex action (for example). This seems like it can't be best practice.
The problem:
The problem is that she is now asking me to add features in which a change to the data in one subcomponent should update the data in a different subcomponent.
For example, she wants the app to work so that when she has the same recipe in several different Meals of the week, then a change to an ingredient in one of the meals will propagate to the other meals that have the same recipe. (image explanation)
My question:
What is the best practice for handling a situation like this? Should I move the ingredient data into the Vuex store or (in the same vein) the lowest-common-ancestor WeeklyMenu.vue component? If so, how exactly should it work? Should there be a separate variable for each meal? Or should I have an object that contains data for all of the different meals? If I use a single object, do I need to worry that a watcher on that object in the Meal.vue component would be triggering even when a change was made to a different meal's data?
If I store all the meal ingredients in separate variables, I would need to pass all of those to every meal (so every meal would need to receive every other meal's ingredients as separate props). So that doesn't seem like the right way to go.
If a user is making a particular change to a particular meal, how would I only have the other meals with the same name react?
Related links:
Communication between sibling components in VueJs 2.0
I'm looking into whether it would make sense to move the ingredient data up to the level of the WeeklyMenu.vue component as described in the "Lowest Common Ancestor" approach (here and here).
Simplified example of the situation I'm trying to handle:
Without Vuex: https://codepen.io/NathanWailes/pen/zYBGjME
Using Vuex: https://codepen.io/NathanWailes/pen/WNxWxWe
With everything working (including the state being kept in Vuex) except the propagation: https://codepen.io/NathanWailes/pen/KKMYNVZ
Yes, problem domain seems complex enough to more than justify use of Vuex. I would not go with keeping data in components and sharing by props - that doesn't scale well
Keep each Recipe as an object in single object recipes - you don't need to worry about watchers. If one particular Recipe object will change, Vue will re-render only components using same Recipe object (and if done properly you don't even need watchers for that)
Create a "weekly menu" object inside the store
In leaf nodes (Meals) of that object just use some kind of reference (by name or unique ID if you have one) into recipes. As a result multiple Meal.vue components on a menu will use same object in the store and update automatically
I ended up getting it working in a simple example in CodePen, which I'm going to use as a guide when trying to get it working on the actual site.
The summary of my findings with this solution is, "Vue will actually update when the nested entries of a Vuex state object are updated; you don't need to worry about it not detecting those changes. So it's OK to just keep all the data in a single big Vuex store object when you have many duplicate sibling components that need to react to each other."
Here's the CodePen: https://codepen.io/NathanWailes/pen/NWRNgNz
Screenshot
Summary of what the CodePen example does
The data used to populate the menu all lives in the Vuex store in a single weeklyMenu object, which has child objects to break up the data into the different days / meals.
The individual meals have computed properties with get and set functions so that it can both get changes from the store and also update the store.
The DailyMenu and WeeklyMenu components get their aggregate data by simply having computed properties that iterate over the Vuex weeklyMenu object, and it "just works".
I have same-named meals update to match each other by iterating over the meals in the Vuex mutation and looking for meals with the same "Ingredient Name".
The code
HTML
<html>
<body>
<div id='weekly-menu'></div>
<h3>Requirements:</h3>
<ul>
<li>Each row should have all the numbers in it summed and displayed ('total daily calories').</li>
<li>The week as a whole should have all the numbers summed and displayed ('total weekly calories').</li>
<li>If two or more input boxes have the same text, a change in one numerical input should propagate to the other same-named numerical inputs.</li>
<li>Ideally the data (ingredient names and calories) should be stored in one place (the top-level component or a Vuex store) to make it more straightforward to populate it from the database with a single HTTP call (which is not simulated in this example).</li>
</ul>
</body>
</html>
JavaScript
const store = new Vuex.Store(
{
state: {
weeklyMenu: {
Sunday: {
Breakfast: {
name: 'aaa',
calories: 1
},
Lunch: {
name: 'bbb',
calories: 2
},
},
Monday: {
Breakfast: {
name: 'ccc',
calories: 3
},
Lunch: {
name: 'ddd',
calories: 4
},
}
}
},
mutations: {
updateIngredientCalories (state, {dayOfTheWeekName, mealName, newCalorieValue}) {
state.weeklyMenu[dayOfTheWeekName][mealName]['calories'] = newCalorieValue
const ingredientNameBeingUpdated = state.weeklyMenu[dayOfTheWeekName][mealName]['name']
for (const dayOfTheWeekName of Object.keys(state.weeklyMenu)) {
for (const mealName of Object.keys(state.weeklyMenu[dayOfTheWeekName])) {
const mealToCheck = state.weeklyMenu[dayOfTheWeekName][mealName]
const ingredientNameToCheck = mealToCheck['name']
if (ingredientNameToCheck === ingredientNameBeingUpdated) {
mealToCheck['calories'] = newCalorieValue
}
}
}
},
updateIngredientName (state, {dayOfTheWeekName, mealName, newValue}) {
state.weeklyMenu[dayOfTheWeekName][mealName]['name'] = newValue
}
}
}
)
var Meal = {
template: `
<td>
<h4>{{ mealName }}</h4>
Ingredient Name: <input v-model="ingredientName" /><br/>
Calories: <input v-model.number="ingredientCalories" />
</td>
`,
props: [
'dayOfTheWeekName',
'mealName'
],
computed: {
ingredientCalories: {
get () {
return this.$store.state.weeklyMenu[this.dayOfTheWeekName][this.mealName]['calories']
},
set (value) {
if (value === '' || value === undefined || value === null) {
value = 0
}
this.$store.commit('updateIngredientCalories', {
dayOfTheWeekName: this.dayOfTheWeekName,
mealName: this.mealName,
newCalorieValue: value
})
}
},
ingredientName: {
get () {
return this.$store.state.weeklyMenu[this.dayOfTheWeekName][this.mealName]['name']
},
set (value) {
this.$store.commit('updateIngredientName', {
dayOfTheWeekName: this.dayOfTheWeekName,
mealName: this.mealName,
newValue: value
})
}
}
}
};
var DailyMenu = {
template: `
<tr>
<td>
<h4>{{ dayOfTheWeekName }}</h4>
Total Daily Calories: {{ totalDailyCalories }}
</td>
<meal :day-of-the-week-name="dayOfTheWeekName" meal-name="Breakfast" />
<meal :day-of-the-week-name="dayOfTheWeekName" meal-name="Lunch" />
</tr>
`,
props: [
'dayOfTheWeekName'
],
data: function () {
return {
}
},
components: {
meal: Meal
},
computed: {
totalDailyCalories () {
let totalDailyCalories = 0
for (const mealName of Object.keys(this.$store.state.weeklyMenu[this.dayOfTheWeekName])) {
totalDailyCalories += this.$store.state.weeklyMenu[this.dayOfTheWeekName][mealName]['calories']
}
return totalDailyCalories
}
}
};
var app = new Vue({
el: '#weekly-menu',
template: `<div id="weekly-menu" class="container">
<div class="jumbotron">
<h2>Weekly Menu</h2>
Total Weekly Calories: {{ totalWeeklyCalories }}
<table class="table">
<tbody>
<daily_menu day-of-the-week-name="Sunday" />
<daily_menu day-of-the-week-name="Monday" />
</tbody>
</table>
</div>
</div>
`,
data: function () {
return {
}
},
computed: {
totalWeeklyCalories () {
let totalWeeklyCalories = 0
for (const dayOfTheWeekName of Object.keys(this.$store.state.weeklyMenu)) {
let totalDailyCalories = 0
for (const mealName of Object.keys(this.$store.state.weeklyMenu[dayOfTheWeekName])) {
totalDailyCalories += this.$store.state.weeklyMenu[dayOfTheWeekName][mealName]['calories']
}
totalWeeklyCalories += totalDailyCalories
}
return totalWeeklyCalories
}
},
components: {
daily_menu: DailyMenu
},
store: store
});

Working with Vuex with Vue and displaying data

This is kind of a long explanation of an issue that I'm having on a personal project. Basically, I want to set a data property before my page loads when I read in data from a CSV file using D3.JS. I almost have it done but running into a small issue. Please read on to get more detail.
Basically, when the user comes to a page in my application, I want to display weather graphs. Like I said, I'm using D3.js to read in the data and created an action to do that. It works perfectly fine-I can console.log the data and I know its been read. However, in my vue instance I have a data property, which would hold the data set like this:
data() {
return {
name: this.$store.state.name
weatherData: this.$store.state.yearData
}
}
I then want to ensure that the weatherData is filled, with data from the csv file so I display it on the page like this:
<p>{{ weatherData }}</p>
Nothing special here. When the page loads, weatherData is blank. But I have a beforeMount life cycle hook and if I comment out the only line in it then it will display the data. If I then refresh the page, fire the action to get the data and then uncomment out the line in the beforeMount hook then the data appears! So before I continue this is my full code for the store:
export const store = new Vuex.Store({
state: {
name: 'Weather Data'
yearData: []
},
getters: {
},
mutations: {
setYearData(state, data) {
state.yearData = data
}
},
actions: {
getYearData: ({commit}) => {
d3.csv("../src/components/data/alaska.csv")
.then(function(data){
let yearData = []
for (let i = 0; i < data.length; i++){
let day = data[i].AKST
yearData.push(day)
}
//console.log(yearData)
commit('setYearData', yearData)
})
}
})
Here are parts of the vue file: The template:
<p>{{ weatherData }}</p>
The Vue Intance:
export default {
name: 'Weather',
data() {
return {
name: this.$store.state.name,
weatherData: this.$store.state.yearData
}
},
methods: {
...mapActions([
'getYearData'
])
},
beforeMount(){
this.$store.dispatch('getYearData') //(un)Commenting out this line will make my data appear
}
}
Page when it loads: Notice empty array:
Then either comment out or comment the one line in the beforeMount hook and get this: THE DATA!!!
Again, my end goal is to have the action called and the data set before the page finishes loading. Finally, I know that I don't need VUEX but this project is further helping me understand it. Any guidance on why this is happening would be great.
use mapState instead of putting your data in the data object, which sometimes being late on updating the template.
just make your Vue instance to look like:
import {mapState} from 'vuex'
export default {
name: 'Weather',
data() {
return { }
},
computed:{
...mapState({
name: state=>state.name,
weatherData: state=>state.yearData
})
},
methods: {
...mapActions([
'getYearData'
])
},
beforeMount(){
this.$store.dispatch('getYearData') //(un)Commenting out this line will make my data appear
}
thats way, you work directly with one source of truth-the store, and your name and weatherData will be reactive as well.
more about mapState here: https://vuex.vuejs.org/guide/state.html#the-mapstate-helper

rendering vue.js components and passing in data

I'm having trouble figuring out how to render a parent component, display a list of contracts in a list on part of the page, and when a user clicks on one of them, display the details of that specific contract on the other part of the page.
Here is my slim file:
#contracts_area
.filter-section
ul
li.filter-item v-for="contract in contractsAry" :key="contract.id" #click="showContract(contract)"
| {{ contract.name }}
.display-section
component :is="currentView" transition="fade" transition-mode="out-in"
script type="text/x-template" id="manage-contracts-template"
div
h1 Blank when page is newly loaded for now
script type="text/x-template" id="view-contract-template"
div :apply_contract="showContract"
h1#display-item__name v-name="name"
javascript:
Vue.component('manage-template', {
template: '#manage-contracts-template'
});
Vue.component('view-contract', {
template: '#view-contract-template',
props: ['show_contract'],
data: function() {
return {
name: ''
}
},
methods: {
showContract: function(contract) {
return this.name = contract.name
}
}
});
Vue.http.headers.common['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content');
var contractsResource = Vue.resource('/all_contracts{/id}.json');
var contracts = new Vue({
el: '#contracts_area',
data: {
currentView: 'manage-template',
contractsAry: [],
errors: {}
},
mounted: function() {
var that = this;
contractsResource.get().then(
function(res) {
that.contractsAry = res.data;
}
)
},
methods: {
showContract: function(contract) {
this.currentView = 'view-contract'
}
}
});
Basically I'd like it so that when a user clicks on any contract item in the .filter-section, it shows the data for that contract in the .display-section. How can I achieve this?
In short you can bind a value to a prop.
.display-section
component :is="currentView" :contract="currentContract"
view-contract
props: ['contract']
contracts-area
data: {
currentContract: null,
},
methods: {
showContract: function(contract) {
this.currentView = "view-contract";
this.currentContract = contract;
}
}
There are multiple ways to pass data in Vue.
Binding values to props.
Using ref to directly call a method from a child component.
Custom Events. Note that to pass events globally, you will need a global event bus.
A single central source of truth (i.e. vuex)
I have illustrated methods 1, 2, 3 in Codepen
Note that 2nd and 3rd methods will only work after your component has been rendered. In your case, since your components for currentView are dynamic and when user clicked, display-section component does not yet exists; it will not receive any events yet. So their content will be empty at first.
To workaround this you can directly access $parent in mounted() from child component, however this would create coupling between them. Another solution is creating the components but conditionally displaying them. And one another solution would be waiting until child component has been mounted and then emitting events.
If your needs are simple I suggest binding values to props (1), else you may consider using something like vuex.

Passing data to components in vue.js

I'm struggling to understand how to pass data between components in vue.js. I have read through the docs several times and looked at many vue related questions and tutorials, but I'm still not getting it.
To wrap my head around this, I am hoping for help completing a pretty simple example
display a list of users in one component (done)
send the user data to a new component when a link is clicked (done) - see update at bottom.
edit user data and send it back to original component (haven't gotten this far)
Here is a fiddle, which fails on step two: https://jsfiddle.net/retrogradeMT/d1a8hps0/
I understand that I need to use props to pass data to the new component, but I'm not sure how to functionally do it. How do I bind the data to the new component?
HTML:
<div id="page-content">
<router-view></router-view>
</div>
<template id="userBlock" >
<ul>
<li v-for="user in users">{{user.name}} - <a v-link="{ path: '/new' }"> Show new component</a>
</li>
</ul>
</template>
<template id="newtemp" :name ="{{user.name}}">
<form>
<label>Name: </label><input v-model="name">
<input type="submit" value="Submit">
</form>
</template>
js for main component:
Vue.component('app-page', {
template: '#userBlock',
data: function() {
return{
users: []
}
},
ready: function () {
this.fetchUsers();
},
methods: {
fetchUsers: function(){
var users = [
{
id: 1,
name: 'tom'
},
{
id: 2,
name: 'brian'
},
{
id: 3,
name: 'sam'
},
];
this.$set('users', users);
}
}
})
JS for second component:
Vue.component('newtemp', {
template: '#newtemp',
props: 'name',
data: function() {
return {
name: name,
}
},
})
UPDATE
Ok, I've got the second step figured out. Here is a new fiddle showing the progress: https://jsfiddle.net/retrogradeMT/9pffnmjp/
Because I'm using Vue-router, I don't use props to send the data to a new component. Instead, I need set params on the v-link and then use a transition hook to accept it.
V-link changes see named routes in vue-router docs:
<a v-link="{ name: 'new', params: { name: user.name }}"> Show new component</a>
Then on the component, add data to the route options see transition hooks:
Vue.component('newtemp', {
template: '#newtemp',
route: {
data: function(transition) {
transition.next({
// saving the id which is passed in url
name: transition.to.params.name
});
}
},
data: function() {
return {
name:name,
}
},
})
-------------Following is applicable only to Vue 1 --------------
Passing data can be done in multiple ways. The method depends on the type of use.
If you want to pass data from your html while you add a new component. That is done using props.
<my-component prop-name="value"></my-component>
This prop value will be available to your component only if you add the prop name prop-name to your props attribute.
When data is passed from a component to another component because of some dynamic or static event. That is done by using event dispatchers and broadcasters. So for example if you have a component structure like this:
<my-parent>
<my-child-A></my-child-A>
<my-child-B></my-child-B>
</my-parent>
And you want to send data from <my-child-A> to <my-child-B> then in <my-child-A> you will have to dispatch an event:
this.$dispatch('event_name', data);
This event will travel all the way up the parent chain. And from whichever parent you have a branch toward <my-child-B> you broadcast the event along with the data. So in the parent:
events:{
'event_name' : function(data){
this.$broadcast('event_name', data);
},
Now this broadcast will travel down the child chain. And at whichever child you want to grab the event, in our case <my-child-B> we will add another event:
events: {
'event_name' : function(data){
// Your code.
},
},
The third way to pass data is through parameters in v-links. This method is used when components chains are completely destroyed or in cases when the URI changes. And i can see you already understand them.
Decide what type of data communication you want, and choose appropriately.
The best way to send data from a parent component to a child is using props.
Passing data from parent to child via props
Declare props (array or object) in the child
Pass it to the child via <child :name="variableOnParent">
See demo below:
Vue.component('child-comp', {
props: ['message'], // declare the props
template: '<p>At child-comp, using props in the template: {{ message }}</p>',
mounted: function () {
console.log('The props are also available in JS:', this.message);
}
})
new Vue({
el: '#app',
data: {
variableAtParent: 'DATA FROM PARENT!'
}
})
<script src="https://unpkg.com/vue#2.5.13/dist/vue.min.js"></script>
<div id="app">
<p>At Parent: {{ variableAtParent }}<br>And is reactive (edit it) <input v-model="variableAtParent"></p>
<child-comp :message="variableAtParent"></child-comp>
</div>
I think the issue is here:
<template id="newtemp" :name ="{{user.name}}">
When you prefix the prop with : you are indicating to Vue that it is a variable, not a string. So you don't need the {{}} around user.name. Try:
<template id="newtemp" :name ="user.name">
EDIT-----
The above is true, but the bigger issue here is that when you change the URL and go to a new route, the original component disappears. In order to have the second component edit the parent data, the second component would need to be a child component of the first one, or just a part of the same component.
The above-mentioned responses work well but if you want to pass data between 2 sibling components, then the event bus can also be used.
Check out this blog which would help you understand better.
supppose for 2 components : CompA & CompB having same parent and main.js for setting up main vue app. For passing data from CompA to CompB without involving parent component you can do the following.
in main.js file, declare a separate global Vue instance, that will be event bus.
export const bus = new Vue();
In CompA, where the event is generated : you have to emit the event to bus.
methods: {
somethingHappened (){
bus.$emit('changedSomething', 'new data');
}
}
Now the task is to listen the emitted event, so, in CompB, you can listen like.
created (){
bus.$on('changedSomething', (newData) => {
console.log(newData);
})
}
Advantages:
Less & Clean code.
Parent should not involve in passing down data from 1 child comp to another ( as the number of children grows, it will become hard to maintain )
Follows pub-sub approach.
I've found a way to pass parent data to component scope in Vue, i think it's a little a bit of a hack but maybe this will help you.
1) Reference data in Vue Instance as an external object (data : dataObj)
2) Then in the data return function in the child component just return parentScope = dataObj and voila. Now you cann do things like {{ parentScope.prop }} and will work like a charm.
Good Luck!
I access main properties using $root.
Vue.component("example", {
template: `<div>$root.message</div>`
});
...
<example></example>
A global JS variable (object) can be used to pass data between components. Example: Passing data from Ammlogin.vue to Options.vue. In Ammlogin.vue rspData is set to the response from the server. In Options.vue the response from the server is made available via rspData.
index.html:
<script>
var rspData; // global - transfer data between components
</script>
Ammlogin.vue:
....
export default {
data: function() {return vueData},
methods: {
login: function(event){
event.preventDefault(); // otherwise the page is submitted...
vueData.errortxt = "";
axios.post('http://vueamm...../actions.php', { action: this.$data.action, user: this.$data.user, password: this.$data.password})
.then(function (response) {
vueData.user = '';
vueData.password = '';
// activate v-link via JS click...
// JSON.parse is not needed because it is already an object
if (response.data.result === "ok") {
rspData = response.data; // set global rspData
document.getElementById("loginid").click();
} else {
vueData.errortxt = "Felaktig avändare eller lösenord!"
}
})
.catch(function (error) {
// Wu oh! Something went wrong
vueData.errortxt = error.message;
});
},
....
Options.vue:
<template>
<main-layout>
<p>Alternativ</p>
<p>Resultat: {{rspData.result}}</p>
<p>Meddelande: {{rspData.data}}</p>
<v-link href='/'>Logga ut</v-link>
</main-layout>
</template>
<script>
import MainLayout from '../layouts/Main.vue'
import VLink from '../components/VLink.vue'
var optData = { rspData: rspData}; // rspData is global
export default {
data: function() {return optData},
components: {
MainLayout,
VLink
}
}
</script>

Categories

Resources