Vue fails to render data object which has a parent - javascript

I need a JS tree object in which child nodes know who their parents are. Vue seems OK with this, at least as far as displaying the correct infinitely expandable object in Vue Devtools goes:
... and displaying the correct parent in the console:
The problem is that Vue errors when trying to render that data on a page:
HTML
<ol>
<li>
<b>family.name</b> {{ family.name }}
</li>
<li>
<b>family.children[0].name</b> {{ family.children[0].name }}
</li>
<li>
<b>family.children[1].name</b> {{ family.children[1].name }}
</li>
<li>
<!-- Add the extra brackets to this to see the "object" error -->
<b>family.children[0].parent.name</b> { family.children[0].parent.name }
</li>
</ol>
JS
var app = new Vue({
el: '.container',
data: {
family: {
name: "Dad",
children: [
{
name: "Marcus",
children: [
{
name: "Bob"
}
]
},
{
name: "Anna",
children: [
{
name: "Ringo"
}
]
}
]
}
},
mounted() {
this.family.children[0].parent = this.family;
this.family.children[0].children[0].parent = this.family.children[0];
this.family.children[1].parent = this.family;
this.family.children[1].children[0].parent = this.family.children[1];
}
});
There is a live example if you prefer at https://s3-ap-southeast-2.amazonaws.com/mscau/vue-parenting.htm.
Does anyone know how to overcome this obstacle?

You are setting up that relationship in mounted() lifecycle hook which occurs after the first render, so the render will fail before you even set this up.
If you do this work inside created() hook instead, it will set it up before the first render to avoid the failure.
See lifecycle diagram for more detailed info about these hooks.

Related

Vue 3 passing array warning: Extraneous non-props attributes were passed to component but could not be automatically inherited

please, I'm learning a VueJS 3 and I have probably begineer problem. I have warn in browser developer console like this one:
The Message is:
[Vue warn]: Extraneous non-props attributes (class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.
I'm passing array of objects to the child Component. In my parent views/Home.vue compoment I have this implemenation:
<template>
<div class="wrapper">
<section v-for="(item, index) in items" :key="index" class="box">
<ItemProperties class="infobox-item-properties" :info="item.properties" />
</section>
</div>
</template>
<script>
import { ref } from 'vue'
import { data } from '#/data.js'
import ItemProperties from '#/components/ItemProperties.vue'
export default {
components: {
ItemDescription,
},
setup() {
const items = ref(data)
return {
items,
}
},
</script>
In child compoment components/ItemProperties.vue I have this code:
<template>
<div class="infobox-item-property" v-for="(object, index) in info" :key="index">
<span class="infobox-item-title">{{ object.name }}:</span>
<span v-if="object.type === 'rating'">
<span v-for="(v, k) in object.value" :key="k">{{ object.icon }}</span>
</span>
<span v-else>
<span>{{ object.value }}</span>
</span>
</div>
</template>
<script>
export default {
props: {
info: {
type: Array,
required: false,
default: () => [
{
name: '',
value: '',
type: 'string',
icon: '',
},
],
},
},
}
</script>
It doesn't matter if I have default() function or not. Also doesn't matter if I have v-if condition or not. If I have cycle in the Array, I got this warning
Data are in data.js file. The part of file is here:
export const data = [
{
title: 'White shirt',
properties: [
{ name: 'Material', value: 'Cotton', type: 'string', icon: '' },
{ name: 'Size', value: 'M', type: 'string', icon: '' },
{ name: 'Count', value: 4, type: 'number', icon: '' },
{ name: 'Absorption', value: 4, type: 'rating', icon: '💧' },
{ name: 'Rating', value: 2, type: 'rating', icon: '⭐️' },
{ name: 'Confort', value: 2, type: 'rating', icon: '🛏' },
{ name: 'Sleeves', value: 'Short', type: 'string', icon: '' },
{ name: 'Color', value: 'White', type: 'string', icon: '' },
],
},
]
PS: Application works but I'm afraid about that warning. What can I do please like right way?
I will be glad for any advice. Thank you very much.
Well I think the error message is pretty clear.
Your ItemProperties.vue component is rendering fragments - because it is rendering multiple <div> elements using v-for. Which means there is no single root element.
At the same time, you are passing a class to the component with <ItemProperties class="infobox-item-properties" - class can be placed on HTML elements only. If you place it on Vue component, Vue tries to place it on the root element of the content the component is rendering. But because the content your component is rendering has no root element, Vue does not know where to put it...
To remove the warning either remove the class="infobox-item-properties" or wrap the content of ItemProperties to a single <div>.
The mechanism described above is called Fallthrough Attributes ("Non-prop attributes" Vue 2 docs). It is good to know that this automatic inheritance can be switched off which allows you to apply those attributes by yourself on the element (or component) you choose besides the root element. This can be very useful. Most notably when designing specialized wrappers around standard HTML elements (like input or button) or some library component...
The ItemProperties component has multiple root nodes because it renders a list in the root with v-for.
Based on the class name (infobox-item-properties), I think you want the class to be applied to a container element, so a simple solution is to just add that element (e.g., a div) in your component at the root:
// ItemProperties.vue
<template>
<div>
<section v-for="(item, index) in items" :key="index" class="box">
...
</section>
</div>
</template>
demo
You could also prevent passing down attributes in child components by doing this:
export default defineComponent({
name: "ChildComponentName",
inheritAttrs: false // This..
})
Source: https://vuejs.org/guide/components/attrs.html
This could also be triggered from parent components that have props: true in their route definition. Make sure that you add props: true only in the components that you actually need it and have some route params as props.
You are passing a class attribute to ItemProperties without declaring it.
Declare class in props options api should solve this issue.
ItemProperties.vue
...
export default {
props:["class"],
...
}

How can I use a component function in Vue to remove an object from an array in the parent's data?

I think it's easier for you to just see the relevant VueJS code and then I can explain it:
new Vue({
el: '#app',
data: {
history: [
{name: 'red', value: '#f00'},
{name: 'not red', value: '#ff0'},
{name: 'also not red', value: '#f0f'},
],
},
components: {
ColorItem: {
template:
`<div>
<input :value="name">
<div class="color-preview" :style="{backgroundColor:hex}"></div>
<span v-html="hex"></span>
<button #click="$emit('remove')">
<i class="fas fa-trash"></i>
</button>
</div>`,
props:
['mode', 'hex', 'name'],
methods: {
removeColor: function(index) {
console.log(index);
this.history.splice(index, 1);
}
},
}
},
// ...
}
I have objects (representing colors with names and values) in an array in a variable called history in my Vue app. I'm using v-for to create a new color-item component for each item in history:
<div v-for="(item, index) in history" :key="item.value">
<color-item mode="history" :name="item.name" :hex="item.value" #remove="removeColor(index)">
</color-item>
</div>
I'm trying to delete the color from the list, and I saw this beautiful example of how to use vue to remove items from a list, and it uses their position and splices it. I also saw this SO answer on getting the position using the map function, however pos is undefined for me, because e.hex is undefined, and using the inspector I think it's because Vue uses some sort of getter under the hood and doesn't just have the data there for me.
Before someone tells me to use the component template in the v-for loop, I need the template so I can reuse this for other lists of colors (for example, favorites).
I'm very new to Vue so pardon my improper wordings, and I appreciate all the help I can get learning this framework.
Generally, the child component should not be manipulating data of the parent component directly, especially via this.$parent.whatever. You should keep the boundaries of the components distinct, otherwise you'll make them tightly-coupled.
All the child component needs to do is emit a remove event that tells the parent that it should remove that item from its own data (which the parent owns).
In the child component:
<button #click="$emit('remove')">Remove</button>
Then in the parent component:
<div v-for="item of history">
<color-item :hex="item.hex" #remove="removeItem(item)"/>
</div>
methods: {
removeItem(item) {
this.history = this.history.filter(otherItem => otherItem !== item)
// or
this.history.splice(this.history.indexOf(item), 1)
}
}
The event handler for the remove event passes in the item to remove as an argument.
Since the parent component "owns" the history array, it should be the only component that mutates it. Once you start allowing random components to mutate data they do not own, then you start getting spaghetti code and it can be difficult to track down why a particular mutation happened and who mutated it.
Here is an example:
Vue.component('color-item', {
template: '#color-item',
props: ['name', 'hex'],
})
new Vue({
el: '#app',
data: {
items: [
{ name: 'red', value: '#f00' },
{ name: 'green', value: '#0f0' },
{ name: 'blue', value: '#00f' },
],
},
methods: {
removeItem(item) {
this.items.splice(this.items.indexOf(item), 1)
},
},
})
.name {
display: inline-block;
min-width: 50px;
}
.preview {
display: inline-block;
width: 15px;
height: 15px;
margin-right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="color-item">
<div>
<span class="name">{{ name }}</span>
<div class="preview" :style="{ backgroundColor: hex }"></div>
<button #click="$emit('remove')">Remove</button>
</div>
</template>
<div id="app">
<div v-for="item of items">
<color-item :name="item.name" :hex="item.value" #remove="removeItem(item)"></color-item>
</div>
</div>
The better approach here is to use $emit from the child component and listening to the event in the parent component.
You should pass pos as an event then change the history data directly in parent component
In your removeColor function:
this.$emit('remove-color', pos)
Then in parent component you should have:
<ColorItem v-on:remove-color="removeColorFromHistory" />
And change your data in methods of the parent component:
methods: {
removeColorFromHistory(pos) {
this.history.splice(pos, 1);
}
}
(see documents)
$parent is meant for handling of edge cases, meaning unusual situations that sometimes require bending Vue’s rules a little. Note however, that they all have disadvantages or situations where they could be dangerous. *

Mapping an array inside of a react component after importing arrray from file

I have a react component. I also have a js file that has an array of objects which is imported into the component. I want to use the .map() function on the array and display each piece of information inside li whic is currently inside of the ul that wraps around it. I know the Activity is imported correctly because console.log(Activity) is displaying the 5 items. When it gets to the map function, it is deciding not to iterate and display any of the infromation. I looked at the source code and it just shows an empty unordered list.
import React, { Component } from 'react';
import Activities from '../../../../fakedata/fake_activity';
export default function Activity(props){
console.log(Activities)
return(
<div className="activity-container">
<ul className="activity-list">
{
Activities.map((activity) => {
<li className="activity">
<span className="activity-group">{activity.group}</span>
<span className="activity-date">{activity.date}</span>
<span className="activity-description">{activity.description}</span>
</li>
})
}
</ul>
</div>
)
}
this is the activity object being imported
const activity = [
{
group: 'Homiez',
description: '',
date: '3:22 pm 1/15'
},
{
group: 'Bros',
description: '',
date: '1:48 pm 1/15'
},
{
group: 'Bros',
description: '',
date: '12:31 pm 1/15'
},
{
group: 'Homiez',
description: '',
date: '10:13 am 1/15'
},
{
group: 'Manayek',
description: '',
date: '7:17 pm 1/14'
},
]
module.exports = activity;
You're not returning anything from inside your map function. You either need to change it to be like this:
Activities.map((activity) => {
return (
<li className="activity">
<span className="activity-group">{activity.group}</span>
<span className="activity-date">{activity.date}</span>
<span className="activity-description">{activity.description}</span>
</li>
);
})
or this:
Activities.map((activity) => (
<li className="activity">
<span className="activity-group">{activity.group}</span>
<span className="activity-date">{activity.date}</span>
<span className="activity-description">{activity.description}</span>
</li>
))
This q+a might help you understand why: React elements and fat arrow functions

VueJS2 how to dynamically emit event to parent component?

just like the title i need to dynamically emit an event to parent component's methods, i have a component structured like this
<TableComponent
:actionmenus="actionmenus"
#edit="parenteditmethod"
#delete="parentdeletemethod">
</TableComponent>
here is actionmenus object
actionmenus: [
{title: 'Edit', emitevent: 'edit'},
{title: 'Delete', emitevent: 'delete'}
]
and then here is snippet of my tablecomponent
...
<ul>
<li v-for="menu in actionmenus"><a #click="$emit(menu.emitevent)" class="text-menu">{{ menu.title }}</a></li>
</ul>
...
i know this should be easily done by $emit('edit') or $emit('delete') without using actionmenus object but the $emit() part should be dynamic based on the passed array actionmenus so that the tablecomponent can be re-used on different case. how should i approaching this? is there any way?
From what I understand, you would like to emit an event from the child component to the parent, and pass some data with the emit (sorry if thats not the case).
As you know, you can emit events in the child component like this :
$emit("EVENT");
And Catch it in the parent like this :
<childTag v-on:EVENT="parentFunction"></childTag>
You can also pass data to the parent from the child like this :
$emit("EVENT",DATA);
And catch the data in the parent function like this
<childTag v-on:EVENT="parentFunction"></childTag>
...
methods{
parentFunction(DATA){
//Handle the DATA object from the child
}
}
Hope this helps and best of luck!
#codincat is right, that it all works. It's true also for Vue3
const rootComponent = {
el: "#app",
data: function () {
return {
actionmenus: [
{ title: "Edit", emitevent: "edit" },
{ title: "Delete", emitevent: "delete" }
]
};
},
methods: {
parenteditmethod() {
console.log("edit");
},
parentdeletemethod() {
console.log("delete");
}
}
};
const app = Vue.createApp(rootComponent);
app.component("table-component", {
props: { actionmenus: Array },
template: `
<ul>
<li v-for="menu in actionmenus">
<a #click="$emit(menu.emitevent)" class="text-menu">{{ menu.title }}</a>
</li>
</ul>`
});
const rootComponentInstance = app.mount("#app");
<!-- https://stackoverflow.com/questions/43750969/vuejs2-how-to-dynamically-emit-event-to-parent-component -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.29/vue.global.min.js"></script>
<div id="app">
<table-component :actionmenus="actionmenus" #edit="parenteditmethod" #delete="parentdeletemethod">
</table-component>
</div>

How to manage state in a tree component in reactjs

I've been struggling this for a couple of days, trying to figure out the "react" way to do it.
Basically, I have a tree, a list of lists (of lists ...) that can be arbitrarily nested, and I want a component that will display this and also enable rearrangement.
Here's my data:
var data = [{
id: 1
}, {
id: 2, children: [
{
id: 3, children: [{id: 6}]
}, {
id: 4
}, {
id: 5
}]
}]
My first pass was to just have a single "tree" component that builds the nested lists of DOM elements in its render function (look at the code here). That actually worked pretty well for small numbers of elements, but I want to be able to support hundreds of elements, and there was a very high re-render cost when an element was moved within the tree (~600ms when there were a few hundred elements).
So I think I'll have each "node" of the tree be it's own instance of this component. But here's my question (sorry for the long intro):
Should each node dynamically query for the list it's children's IDs from a central "database" and store that in state? Or should the top-most node load the whole tree and pass everything down through props?
I'm still trying to wrap my mind around how state & props should be handled & divvied up.
Thanks
I wanted to try out the tree structure with React and came up with a simple component that hides subtrees when you click on <h5>. Everything is a TreeNode. Is this similar to what you were thinking?
You can see it in action in this JSFiddle: http://jsfiddle.net/ssorallen/XX8mw/
TreeNode.jsx:
var TreeNode = React.createClass({
getInitialState: function() {
return {
visible: true
};
},
render: function() {
var childNodes;
if (this.props.node.childNodes != null) {
childNodes = this.props.node.childNodes.map(function(node, index) {
return <li key={index}><TreeNode node={node} /></li>
});
}
var style = {};
if (!this.state.visible) {
style.display = "none";
}
return (
<div>
<h5 onClick={this.toggle}>
{this.props.node.title}
</h5>
<ul style={style}>
{childNodes}
</ul>
</div>
);
},
toggle: function() {
this.setState({visible: !this.state.visible});
}
});
bootstrap.jsx:
var tree = {
title: "howdy",
childNodes: [
{title: "bobby"},
{title: "suzie", childNodes: [
{title: "puppy", childNodes: [
{title: "dog house"}
]},
{title: "cherry tree"}
]}
]
};
React.render(
<TreeNode node={tree} />,
document.getElementById("tree")
);
Seems like it'd be nicer to pass everything down as props, as this will prevent you from the trouble of managing individual insertion/deletion. Also, like the comments said, the key attributes prevents a huge chunk of unnecessary re-rendering.
You might want to check this link: http://facebook.github.io/react/blog/2013/11/05/thinking-in-react.html. It describes the kind of dilemma you're having and how to approach it.
(Coincidentally, I've made a react tree view a while ago: https://github.com/chenglou/react-treeview. Take whatever you want from it!)
Here is a quick example of how to create a treeview using React and Flux.
http://www.syntaxsuccess.com/viewarticle/5510d81be1ce52d00e93da55
The React component is recursive and state is managed using Flux.

Categories