VueJS component throws warning if data has property that parent does not - components

I have a simple component handled by <script type="text/x-template"..., and it has its own data object that has only one property to control whether or not to show a div.
I am getting the following warning in Console:
[Vue warn]: Property or method "showDiv" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in root instance)
Live demo: https://jsbin.com/zucaqog/edit?html,js,output
Js:
Vue.component('my-comp', {
template: '#my-comp',
data: function() {
return { showDiv: false };
}
});
new Vue({
el: '#app',
data: {}
});
If I add showDiv: false to the parent's data object, I no longer receive the warning. But, I'd like to avoid doing that since this data property is relevant only to the local scope of the component. I have a feeling if I put the html of the component as a string in the template: ... then it might work, but, I'd rather have it inside of a script.

It's because you have your child component's template definition as a child of the #app div. The parent component is seeing that there is a reference to the showDiv variable inside of its template, so it's throwing the error.
Pull that x-template outside of the #app div:
<div id="app">
<my-comp></my-comp>
</div>
<script type="text/x-template" id="my-comp">
<div>
Value of showDiv is {{showDiv}}.
<button #click="showDiv=!showDiv">Toggle showDiv</button>
</div>
</script>

Related

React-like refs in lit-html / lit-element?

Does lit-html have by any change something like React's ref feature?
For example in the following pseudo-code inputRef would be a callback function or an object { current: ... } where lit-html could pass/set the HTMLElement instance of the input element when the input element is created/attached.
// that #ref pseudo-attribute is fictional
html`<div><input #ref={inputRef}/></div>`
Thanks.
In lit-element you can use #query property decorator. It's just syntactic sugar around this.renderRoot.querySelector().
import { LitElement, html, query } from 'lit-element';
class MyElement extends LitElement {
#query('#first')
first;
render() {
return html`
<div id="first"></div>
<div id="second"></div>
`;
}
}
lit-html renders directly to the dom so you don't really need refs like you do in react, you can use querySelector to get a reference to the rendered input
Here's some sample code if you were only using lit-html
<html>
<head>
<title>lit-html example</title>
<script type="module">
import { render, html } from 'https://cdn.pika.dev/lit-html/^1.1.2';
const app = document.querySelector('.app');
const inputTemplate = label => {
return html `<label>${label}<input value="rendered input content"></label>`;
};
// rendering the template
render(inputTemplate('Some Label'), app);
// after the render we can access it normally
console.log(app.querySelector('input').value);
</script>
</head>
<body>
<div class="app"></div>
<label>
Other random input
<input value="this is not the value">
</label>
</body>
</html>
If you're using LitElement you could access to the inner elements using this.shadowRoot.querySelector if you're using shadow dom or this.querySelector if you aren't
As #WeiChing has mentioned somewhere above, since Lit version 2.0 you can use the newly added directive ref for that:
https://lit.dev/docs/templates/directives/#ref
-- [EDIT - 6th October 2021] ----------------------------
Since lit 2.0.0 has been released my answer below
is completely obsolete and unnecessary!
Please check https://lit.dev/docs/api/directives/#ref
for the right solution.
---------------------------------------------------------
Even if this is not exactly what I have asked for:
Depending on the concrete use case, one option to consider is the use of directives.
In my very special use-case it was for example (with a little luck and a some tricks) possible to simulate more or less that ref object behavior.
const inputRef = useElementRef() // { current: null, bind: (special-lit-html-directive) }
...
return html`<div><input ref=${inputRef.bind}/></div>`
In my use case I could do the following:
Before rendering, set elementRef.current to null
Make sure that elementRef.current cannot be read while the component is rerendered (elementRef.current is not needed while rendering and an exception will be thrown if someone tries to read it in render phase)
That elementRef.bind directive will fill elementRef.current with the actual DOM element if available.
After that, elementRef.current can be read again.
For lit-html v1, you can define your own custom Derivative:
import { html, render, directive } from "lit-html";
function createRef(){ return {value: null}; }
const ref = directive((refObj) => (attributePart) => {
refObj.value = attributePart.committer.element;
});
const inputRef = createRef();
render(html`<input ref=${ref(inputRef)} />`;
// inputRef.value is a reference to rendered HTMLInputElement

vuejs and vuex || find data from v-for to get another data

I am currently doing my capstone project in our school. Currently I am using nuxt, vue, vuex, and vuetify for my csr and express for ssr.
In my code I want to fetch a data from v-for then pass to my component to query another data..
//my code in parent component
<template>
<v-list-group v-else v-for='(items, index) in halfpayments' :key="index">
<v-list-tile slot='activator'>
<v-list-tile-title><span class="subheading">{{items.ordnameto}}</span></v-list-tile-title>
</v-list-tile>
<v-list-tile>
<halfpay text='black--text' :ids='items.id'/>
</v-list-group>
</template>
<script>
import halfpay from '#/components/custom/fullHalfpay'
export default {
components: {
halfpay
}
}
//code in child component
<template>
<v-flex>
<v-list-tile v-for='(item) in data' :key="item.id">
<v-list-tile-content>
<v-list-tile-sub-title><span class="caption">Full payment date: </span><span :class="text">{{$dateFilter(item.paid_time)}}</span></v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile>
</v-flex>
</template>
<script>
export default{
props: {
ids: {
type: String,
required: true
},
text:{
type: String,
required: true
}
},
computed: {
data(){
return this.$store.getters.halffullpay(this.ids)
}
}
}
</script>
the output of it, the v-for will render all the data of the child component. Is there a way to get the data in specific way. The only way is to merge the 2 separate data but it complicates the logic in the long run because the child node is a conditional render which it need to satisfy a condition to apply the child component.
Use a computed property in your DOM. Your scripts will set this.dataFromAPI each time the API returns data, and your computed property (or multiple) will format that data as needed. Or the computed property of the child component is based off "input" props. (I couldn't quite tell from your code).
.map() it, .reduce() it, whatever you need.
You use the computed property name just like another data prop in your DOM, as if this.computedData was on your data().

A way to render multiple root elements on VueJS with v-for directive

Right now, I'm trying to make a website that shows recent news posts which is supplied my NodeJS API.
I've tried the following:
HTML
<div id="news" class="media" v-for="item in posts">
<div>
<h4 class="media-heading">{{item.title}}</h4>
<p>{{item.msg}}</p>
</div>
</div>
JavaScript
const news = new Vue({
el: '#news',
data: {
posts: [
{title: 'My First News post', msg: 'This is your fist news!'},
{title: 'Cakes are great food', msg: 'Yummy Yummy Yummy'},
{title: 'How to learnVueJS', msg: 'Start Learning!'},
]
}
})
Apparently, the above didn't work because Vue can't render multiple root elements.
I've looked up the VueJS's official manual and couldn't come up with a solution.
After googling a while, I've understood that it was impossible to render multiple root element, however, I yet to have been able to come up with a solution.
The simplest way I've found of adding multiple root elements is to add a single <div> wrapper element and make it disappear with some CSS magic for the purposes of rendering.
For this we can use the "display: contents" CSS property. The effect is that it makes the container disappear, making the child elements children of the element the next level up in the DOM.
Therefore, in your Vue component template you can have something like this:
<template>
<div style="display: contents"> <!-- my wrapper div is rendered invisible -->
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
</div>
</template>
I can now use my component without the browser messing up formatting because the wrapping <div> root element will be ignored by the browser for display purposes:
<table>
<my-component></my-component> <!-- the wrapping div will be ignored -->
</table>
Note however, that although this should work in most browsers, you may want to check here to make sure it can handle your target browser.
You can have multiple root elements (or components) using render functions
A simple example is having a component which renders multiple <li> elements:
<template>
<li>Item</li>
<li>Item2</li>
... etc
</template>
However the above will throw an error. To solve this error the above template can be converted to:
export default {
functional: true,
render(createElement) {
return [
createElement('li', 'Item'),
createElement('li', 'Item2'),
]
}
}
But again as you probably noticed this can get very tedious if for example you want to display 50 li items. So, eventually, to dynamically display elements you can do:
export default {
functional: true,
props: ['listItems'], //this is an array of `<li>` names (e.g. ['Item', 'Item2'])
render(createElement, { props }) {
return props.listItems.map(name => {
return createElement('li', name)
})
}
}
INFO in those examples i have used the property functional: true but it is not required of course to use "render functions". Please consider learning more about functional componentshere
Define a custom directive:
Vue.directive('fragments', {
inserted: function(el) {
const children = Array.from(el.children)
const parent = el.parentElement
children.forEach((item) => { parent.appendChild(item) })
parent.removeChild(el)
}
});
then you can use it in root element of a component
<div v-fragments>
<tr v-for="post in posts">...</tr>
</div>
The root element will not be rendered in DOM, which is especially effective when rendering table.
Vue requires that there be a single root node. However, try changing your html to this:
<div id="news" >
<div class="media" v-for="item in posts">
<h4 class="media-heading">{{item.title}}</h4>
<p>{{item.msg}}</p>
</div>
</div>
This change allows for a single root node id="news" and yet still allows for rendering the lists of recent posts.
In Vue 3, this is supported as you were trying:
In 3.x, components now can have multiple root nodes! However, this does require developers to explicitly define where attributes should be distributed.
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
Multiple root elements are not supported by Vue (which caused by your v-for directive, beacause it may render more than 1 elements). And is also very simple to solve, just wrap your HTML into another Element will do.
For example:
<div id="app">
<!-- your HTML code -->
</div>
and the js:
var app = new Vue({
el: '#app', // it must be a single root!
// ...
})

VueJs component not getting data from its parent instance

I am new to VueJS and I am confused why a simple example from the docs is not working for me.
This is all the code. I am expecting it to display "Howdie Partners" on the page.
HTML
<div id="app">
<greeting></greeting>
</div>
JS
Vue.component('greeting', {
template: '<h1>{{message}}</h1>',
props: ['message']
});
var vm = new Vue({
el: '#app',
data: {
message: "Howdie Partners!"
}
});
JSFiddle Link: https://jsfiddle.net/hq1yu0ct/
From the documentation:
we can also use v-bind for dynamically binding props to data on the parent. Whenever the data is updated in the parent, it will also flow down to the child.
So you need to pass the props in the greeting component like following:
<div id="app">
<greeting :message="message"></greeting>
</div>
check working fiddle here.

jsf render components with js

Let's say I have
<p:outputPanel/>
What I want to do is to specify rendered attr using js method not serverside.
This is for improving performance.
So I need something like :
<p:outputPanel rendered = "someJsFunction()"/>
What is the solution?
rendered propery is processed at server side and if it resolves to false, the element is not added into the html document. So javascript can't even find the element to display or hide because it is not created.
The only thing you can do is to remove the rendered property and change the display property of the element with javascript.
<div id="myDiv">My Content</div>
<button onclick="myFunction()">Click Me</button>
<script>
function myFunction() {
document.getElementById("myDIV").style.display = "none";
}
</script>
Well, you can have the same effect at page load cause rendered attribute is resolved at Server Side only , So using jQuery you can do it like
$(document).ready(function() {
document.getElementById("YourPanelIdHere").style.display = "none";
});
and it will be not displayed.

Resources