Handling default slot on custom component - vue.js

Iam stugging on a problem to handle default slots with render() function.
I have two components, one that passes a string value innerHTML to my custom Component MySub. In MySub i wants to use the default slot to do further stuff with it.
My Parent:
import { defineComponent, h, VNode } from "vue";
import MySub from "./mySub.ts"
export default defineComponent({
render() {
return h(MySub, {}, 'innerHTML')
}
})
My Sub:
import { defineComponent, h, VNode } from "vue";
export default defineComponent({
data() {
return {
value: ''
}
},
mounted() {
if (this.$slots.default && this.$slots.default()[0]) this.value = <string>this.$slots.default()[0].children
},
render() {
return h('div', {}, [this.value + ' "here my added Stuff"'])
}
})
Now to my problem: When i code it like this (above or link below), i get a warning calls: Non-function value encountered for default slot. Prefer function slots for better performance. I know whats todo to get rid of these message and why its exists. Just add a function call to the value in my MyParent return h(MySub, {}, () => 'innerHTML').
But when i do this, it get the following message: Slot "default" invoked outside of the render function: this will not track dependencies used in the slot. Invoke the slot function inside the render function instead. Also here, i know what the message wants to tell me, but i cant find a why to handle these problem.
I hope, i could explain my problem clear enough and somebody know what i can do.
Here is an Playground Example that reproduce exactly my problem.

Dont know its the correct way to reply my own question, but i found and resolved the problem...
The problem isnt occur in the parent component. You always should call default slots within a function call like: () => 'innerHTML' for better performance (like the warn message is calling) cause to not render it, when its empty.
So i have to search for the problem in the child...
And the problem was the function mounted()
m̶o̶u̶n̶t̶e̶d̶(̶)̶ ̶{̶
i̶f̶ ̶(̶t̶h̶i̶s̶.̶$̶s̶l̶o̶t̶s̶.̶d̶e̶f̶a̶u̶l̶t̶ ̶&̶&̶ ̶t̶h̶i̶s̶.̶$̶s̶l̶o̶t̶s̶.̶d̶e̶f̶a̶u̶l̶t̶(̶)̶[̶0̶]̶)̶ ̶t̶h̶i̶s̶.̶v̶a̶l̶u̶e̶ ̶=̶ ̶t̶h̶i̶s̶.̶$̶s̶l̶o̶t̶s̶.̶d̶e̶f̶a̶u̶l̶t̶(̶)̶[̶0̶]̶.̶c̶h̶i̶l̶d̶r̶e̶n̶
}̶,̶
In the render() function, i used this.value that is declared outside in the data() and assigned in the mounted(). So i just had to put it inside like below:
getDefaultSlotValue() {
if (this.$slots.default && this.$slots.default()[0]) this.value = <string>this.$slots.default()[0].children
},
render() {
return h('div', {}, [this.getDefaultSlotValue() + ' "here my added Stuff"'])
}
Iam not sure, but the problem seems to be no problem, when you call the MySub directly (to see in App.vue), cause the mounted() is normally called after rendering. But i dont know, whats vue exactly doing under the hood.
Here an working Playground example

Related

Dynamic import of component fails

I'm trying to implement the recommendation of this article on component dynamic import:
Template:
<component :is="componentLoader" />
Script:
computed: {
componentLoader () {
const modalComponent = 'HeroBanner'
return () => import(`#/components/template-ux/${modalComponent}.vue`)
}
}
But I'm getting an error:
Cannot read property 'value' of null - Occurred while linting
D:\projects\my-project\components\global\Modal.vue:41
What is causing this issue?
EDIT: line #41 is the line starting with return () => import...
It looks like the component that you are loading is missing some props maybe.
You can pass you props dynamically like so:
<component :is="componentLoader.component" v-bind="componentLoader.props"/>
computed: {
componentLoader () {
return {
component: () => import(`#/components/template-ux/${modalComponent}.vue`),
props: {
value: 'Something'
foo: 'Foo'
}
}
}
}
Depending on what's happening in your file, you sometimes cannot dynamically import a component. Let's say if you need some values right when rendering the template. Here, there is a case where you do try to access a value with .value on something not here yet.
Solution would be to import it the classic way, or to make some validations on the elements, for example modal?.inputText?.value thanks to optional chaining (working only in the <script> section btw).
Please show us the content of Modal.vue to see what is happening here.

Vuetify Centralize Rules [duplicate]

The following code has been written to handle an event after a button click
var MainTable = Vue.extend({
template: "<ul>" +
"<li v-for='(set,index) in settings'>" +
"{{index}}) " +
"{{set.title}}" +
"<button #click='changeSetting(index)'> Info </button>" +
"</li>" +
"</ul>",
data: function() {
return data;
}
});
Vue.component("main-table", MainTable);
data.settingsSelected = {};
var app = new Vue({
el: "#settings",
data: data,
methods: {
changeSetting: function(index) {
data.settingsSelected = data.settings[index];
}
}
});
But the following error occurred:
[Vue warn]: Property or method "changeSetting" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in <MainTable>)
Problem
[Vue warn]: Property or method "changeSetting" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in <MainTable>)
The error is occurring because the changeSetting method is being referenced in the MainTable component here:
"<button #click='changeSetting(index)'> Info </button>" +
However the changeSetting method is not defined in the MainTable component. It is being defined in the root component here:
var app = new Vue({
el: "#settings",
data: data,
methods: {
changeSetting: function(index) {
data.settingsSelected = data.settings[index];
}
}
});
What needs to be remembered is that properties and methods can only be referenced in the scope where they are defined.
Everything in the parent template is compiled in parent scope; everything in the child template is compiled in child scope.
You can read more about component compilation scope in Vue's documentation.
What can I do about it?
So far there has been a lot of talk about defining things in the correct scope so the fix is just to move the changeSetting definition into the MainTable component?
It seems that simple but here's what I recommend.
You'd probably want your MainTable component to be a dumb/presentational component. (Here is something to read if you don't know what it is but a tl;dr is that the component is just responsible for rendering something – no logic). The smart/container element is responsible for the logic – in the example given in your question the root component would be the smart/container component. With this architecture you can use Vue's parent-child communication methods for the components to interact. You pass down the data for MainTable via props and emit user actions from MainTable to its parent via events. It might look something like this:
Vue.component('main-table', {
template: "<ul>" +
"<li v-for='(set, index) in settings'>" +
"{{index}}) " +
"{{set.title}}" +
"<button #click='changeSetting(index)'> Info </button>" +
"</li>" +
"</ul>",
props: ['settings'],
methods: {
changeSetting(value) {
this.$emit('change', value);
},
},
});
var app = new Vue({
el: '#settings',
template: '<main-table :settings="data.settings" #change="changeSetting"></main-table>',
data: data,
methods: {
changeSetting(value) {
// Handle changeSetting
},
},
}),
The above should be enough to give you a good idea of what to do and kickstart resolving your issue.
Should anybody land with the same silly problem I had, make sure your component has the 'data' property spelled correctly. (eg. data, and not date)
<template>
<span>{{name}}</span>
</template>
<script>
export default {
name: "MyComponent",
data() {
return {
name: ""
};
}
</script>
In my case the reason was, I only forgot the closing
</script>
tag.
But that caused the same error message.
If you're experiencing this problem, check to make sure you don't have
methods: {
...
}
or
computed: {
...
}
declared twice
It's probably caused by spelling error
I got a typo at script closing tag
</sscript>
Remember to return the property
Another reason of seeing the Property "search" was accessed during render but is not defined on instance is when you forget to return the variable in the setup(){} function
So remember to add the return statement at the end:
export default {
setup(){
const search = ref('')
//Whatever code
return {search}
}
}
Note: I'm using the Composition API
Adding my bit as well, should anybody struggle like me, notice that methods is a case-sensitive word:
<template>
<span>{{name}}</span>
</template>
<script>
export default {
name: "MyComponent",
Methods: {
name() {return '';}
}
</script>
'Methods' should be 'methods'
If you use two times vue instance. Then it will give you this error. For example in app.js and your own script tag in view file. Just use one time
const app = new Vue({
el: '#app',
});
I got this error when I tried assigning a component property to a state property during instantiation
export default {
props: ['value1'],
data() {
return {
value2: this.value1 // throws the error
}
},
created(){
this.value2 = this.value1 // safe
}
}
My issue was I was placing the methods inside my data object. just format it like this and it'll work nicely.
<script>
module.exports = {
data: () => {
return {
name: ""
}
},
methods: {
myFunc() {
// code
}
}
}
</script>
In my case, I wrote it as "method" instead of "methods". So stupid. Wasted around 1 hour.
Some common cases of this error
Make sure your component has the data property spelled correctly
Make sure your template is bot defined within another component’s template.
Make sure you defined the variable inside data object
Make sure your router name in string
Get some more sollution
It is most likely a spelling error of reserved vuejs variables. I got here because I misspelled computed: and vuejs would not recognize my computed property variables. So if you have an error like this, check your spelling first!
I had two methods: in the <script>, goes to show, that you can spend hours looking for something that was such a simple mistake.
if you have any props or imported variables (from external .js file) make sure to set them properly using created like this;
make sure to init those vars:
import { var1, var2} from './constants'
//or
export default {
data(){
return {
var1: 0,
var2: 0,
var3: 0,
},
},
props: ['var3'],
created(){
this.var1 = var1;
this.var2 = var2;
this.var3 = var3;
}
In my case it was a property that gave me the error, the correct writing and still gave me the error in the console. I searched so much and nothing worked for me, until I gave him Ctrl + F5 and Voilá! error was removed. :'v
Look twice the warning : Property _____ was accessed during render but is not defined on instance.
So you have to define it ... in the data function for example which commonly instantiate variables in a Vuejs app. and, it was my case and that way the problem has been fixed.
That's all folk's !
In my case, I forgot to add the return keyword:
computed: {
image(){
this.productVariants[this.selectedVariant].image;
},
inStock(){
this.productVariants[this.selectedVariant].quantity;
}
}
Change to:
computed: {
image(){
return this.productVariants[this.selectedVariant].image;
},
inStock(){
return this.productVariants[this.selectedVariant].quantity;
}
}
In my case due to router name not in string:
:to="{name: route-name, params: {id:data.id}}"
change to router name in string:
:to="{name: 'router-name', params: {id:data.id}}"
In my case I was trying to pass a hard coded text value to another component with:
ChildComponent(:displayMode="formMode")
when it should be:
ChildComponent(:displayMode="'formMode'")
note the single quotes to indicate text instead of calling a local var inside the component.
If you're using the Vue3 <script setup> style, make sure you've actually specified setup in the opening script tag:
<script setup>
I had lapsed into old habits and only created a block with <script>, but it took a while to notice it.
https://v3.vuejs.org/api/sfc-script-setup.html
Although some answers here maybe great, none helped my case (which is very similar to OP's error message).
This error needed fixing because even though my components rendered with their data (pulled from API), when deployed to firebase hosting, it did not render some of my components (the components that rely on data).
To fix it (and given you followed the suggestions in the accepted answer), in the Parent component (the ones pulling data and passing to child component), I did:
// pulled data in this life cycle hook, saving it to my store
created() {
FetchData.getProfile()
.then(myProfile => {
const mp = myProfile.data;
console.log(mp)
this.$store.dispatch('dispatchMyProfile', mp)
this.propsToPass = mp;
})
.catch(error => {
console.log('There was an error:', error.response)
})
}
// called my store here
computed: {
menu() {
return this.$store.state['myProfile'].profile
}
},
// then in my template, I pass this "menu" method in child component
<LeftPanel :data="menu" />
This cleared that error away. I deployed it again to firebase hosting, and voila!
Hope this bit helps you.
It seems there are many scenarios that can trigger this error. Here's another one which I just resolved.
I had the variable actionRequiredCount declared in the data section, but I failed to capitalize the C in Count when passing the variable as a params to a component.
Here the variable is correct:
data: () => {
return{
actionRequiredCount: ''
}
}
In my template it was incorrect (notd the no caps c in "count"):
<MyCustomModule :actionRequiredCount="actionRequiredcount"/>
Hope this helps someone.
Most people do have an error here because of:
a typo or something that they forgot to declare/use
the opposite, did it in several places
To avoid the typo issues, I recommend always using Vue VSCode Snippets so that you don't write anything by hand by rather use vbase, vdata, vmethod and get those parts generated for you.
Here are the ones for Vue3.
You can of course also create your own snippets by doing the following.
Also make sure that you're properly writing all the correct names as shown here, here is a list:
data
props
computed
methods
watch
emits
expose
As for the second part, I usually recommend either searching the given keyword in your codebase. So like cmd + f + changeSetting in OP's case to see if it's missing a declaration somewhere in data, methods or alike.
Or even better, use an ESlint configuration so that you will be warned in case you have any kind of issues in your codebase.
Here is how to achieve such setup with a Nuxt project + ESlint + Prettier for the most efficient way to prevent bad practices while still getting a fast formatting!
One other common scenario is:
You have a component (child) extending another component (parent)
You have a property or a method xyz defined under methods or computed on the parent component.
Your are trying to use parent's xyz, but your child component defines its own methods or computed
Sample code with the problem
// PARENT COMPONENT
export default {
computed() {
abc() {},
xyz() {} // <= needs to be used in child component
},
...
}
// CHILD COMPONENT
export default {
extends: myParentComponent,
computed() {
childProprty1() {},
childProprty2() {}
}
}
The solution
In this case you will need to redefine your xyz computed property under computed
Solution 1:
Redefine xyz and copy the code from the parent component
// CHILD COMPONENT
export default {
extends: myParentComponent,
computed() {
xyz() {
// do something cool!
},
childProprty1() {},
childProprty2() {}
}
}
Solution 2
Redefine xyz property reusing parent component code (no code redundancy)
// CHILD COMPONENT
export default {
extends: myParentComponent,
computed() {
xyz() {
return this.$parent.$options.computed.xyz
},
childProprty1() {},
childProprty2() {}
}
}
For me it happened because I wrote method: instead of methods: (plural). It's a silly mistake but it can happen :)
In my case it was the methods: { } I had put the } before my method functions so for example I had it like this methods: { function , function }, function, function so some of the functions that were out of the curly braces were not included inside the methods function.

vue constructor not having local state

I have this code:
import Vue from 'vue'
import s from 'vue-styled-components'
import Test1x from './test1x'
export default Vue.extend({
name:'test1',
render(){
const Div=s.div`
`
const test1x1=new Test1x()
const test1x2=new Test1x()
const el=
<Div>
{test1x1.state.greeting}
{test1x2.state.greeting}
<button vOn:click={()=>test1x1.commit('change')}>change</button>
<button vOn:click={()=>test1x2.commit('change')}>change</button>
</Div>
return el
}
})
and test1x.js file is as follows:
import withStore from './withStore'
export default withStore({
state: {
greeting:'hola'
},
mutations: {
change(state){state.greeting='hello'}
}
})
and withStore.js file is as follows:
import Vue from 'vue'
export default ({ state, mutations }) => {
return Vue.extend({
data () {
return { state }
},
methods: {
commit (mutationName) {
mutations[mutationName](this.state)
},
},
})
}
Given that code, I assume each greeting will be changed by the corresponding button, separately, individually, but not, when I press a button all two greetings change. Anyone knows why? Thank you in advance.
And even more strange is that while at least code presented before is reactive, I mean, greeting change when pressing a button, code below it is not:
import Vue from 'vue'
import s from 'vue-styled-components'
import withStore from './withStore'
export default Vue.extend({
name:'test1',
render(){
const Div=s.div`
`
const Test1x=withStore({
state: {
greeting:'hola'
},
mutations: {
change(state){
state.greeting='hello'
}
}
})
const test1x1=new Test1x()
const test1x2=new Test1x()
const el=
<Div>
{test1x1.state.greeting}
{test1x2.state.greeting}
<button vOn:click={()=>test1x1.commit('change')}>change</button>
<button vOn:click={()=>test1x2.commit('change')}>change</button>
</Div>
return el
}
})
when pressing button nothing happens, greeting remains with hola instead of hello. Isn't that strange? Anyone knows why? Thanks again.
edit
thanks to #skirtle answer, I solved the issue doing this:
import Vue from 'vue'
import s from 'vue-styled-components'
import Test1 from './test1/test1'
import Test1x from './test1/test1x'
export default Vue.extend({
name:'app',
render(){
const Div=s.div`
`
const test1x1=new Test1x()
const test1x2=new Test1x()
//test1x1.commit('init')
test1x1.state={greeting:'hola'}
test1x2.state={greeting:'hola'}
console.log(test1x1.state)
const el=
<Div>
<Test1 test1x={test1x1}/>
<Test1 test1x={test1x2}/>
</Div>
return el
}
})
and test1.js being this:
import Vue from 'vue'
import s from 'vue-styled-components'
export default Vue.extend({
props:{
test1x:Object
},
name:'test1',
render(){
const Div=s.div`
`
const el=
<Div>
{this.test1x.state.greeting}
<button vOn:click={()=>this.test1x.commit('change')}>changes</button>
</Div>
return el
}
})
and test1x.js being this:
import withStore from './withStore'
export default withStore({
state: null,
mutations: {
change(state){state.greeting='hello'},
init(s){s={greeting:'hola'}
console.log(s)}
}
})
This works. The strange thing now is that if I uncomment test1x1.commit('init') I get an infinite loop, don't know why. If I then comment test1x1.state={greeting:'hola'} I don't get an infinite loop but I get an error that cannot read property greeting of null in test1.js. Anyone knows why this is happening? The thing is test1x1.commit('init') does not change the value test1x1.state, it remains null. Thanks.
Addressing the first problem first.
The problem starts here:
state: {
greeting:'hola'
},
The value of state points to a specific object. That object then gets passed around but at no point is a copy taken. The result is that both test1x1 and test1x2 will have the same object for state.
You can confirm this by adding a bit of console logging:
console.log(test1x1.state === test1x2.state)
The way Vuex handles this problem is to allow state to be a function, just like data:
state () {
return {
greeting:'hola'
}
},
Each time the state function is invoked it will return a new object.
As you aren't using Vuex you would need to ensure that you call the state function at the correct point to generate the relevant object. Something like this:
data () {
if (typeof state === 'function') {
state = state()
}
return { state }
},
So, to your second problem. I'm afraid I don't know what the problem is there. However, I very much doubt that 'when pressing button nothing happens'. It may not update the message but that isn't the same as 'nothing happens'. It should be relatively straightforward to add in some console logging at each stage and to establish exactly what does and doesn't happen. Once you've gathered all of that extra information about precisely what is happening it should be fairly simple to pinpoint precisely where the disconnect is occurring.
My suspicion would be that you've made some other changes to withStore that are causing this new problem. It could also be a file caching problem, so that the code you're running is not the code you think it is. Either way the extra logging should reveal all.
If you need further help with that then please update the question with the extra information gathered via console logging.
Update:
This is why the updated code causes an infinite rendering loop:
Inside the render function there is a call to test1x1.commit('init').
Inside commit it accesses the property this.state. This will add the property this.state as a rendering dependency for the component. It doesn't matter what the current value of this.state is, it's the property itself that is the dependency, not its current value.
On the next line it sets test1x1.state={greeting:'hola'}. This changes the value of the state property. This is the same state that has just been registered as a rendering dependency. As a rendering dependency has now changed the component will be re-added to the rendering queue, even though it hasn't finished the current rendering yet.
Eventually Vue will work its way through the rendering queue and get back to this same component. It will again call render to try to render the component. The previous steps will all occur again and so the component keeps being rendered over and over.
The bottom line here is that you shouldn't be initialising these data structures within the render function in the first place. There are various places you might create them but inside render does not appear to be appropriate based on the code you've provided.

Vue: event-bus handler is undefined

I want to implement event-bus in my Vue app.
First I want to make event-bus calls to start loading.
My main component's script tag where I call the $on look like this:
<script>
import store from '../store.js'
import EventBus from '../event-bus';
import Header from './includes/Header'
import Footer from './includes/Footer'
import Loading from './includes/Loading'
export default {
data() {
return {
isLoading: null
}
},
components: {
Header,
Footer,
Loading
},
mounted() {
EventBus.$on('isLoading'), function (payLoad) {
console.log(payload)
this.isLoading = payload
};
}
}
</script>
But I got this error:
[Vue warn]: Error in event handler for "isLoading": "TypeError: handler is >undefined"
How can I fix it?
You've got two mistakes. It should look like this:
EventBus.$on('isLoading', (payLoad) => {
console.log(payload)
this.isLoading = payload
});
Mistake 1 is the closing ) before the comma, leading to $on being called without a function. This is what is causing the warning message. That closing ) should be at the end, between the } and the ;.
Mistake 2 is using a normal function, leading to this being incorrect inside the function. An arrow function will preserve the this value from the surrounding scope.
I suggest using an IDE or a linter that can pick up on these kinds of problems. Your code was syntactically valid but these kinds of mistakes can easily be detected by suitable tooling.

vuejs handsontable official and calling handsontable method

I'm a beginner, this is probably more of a javascript problem than vue but anyway:
there a plugin for spreadsheet named handsontable and in the normal use you make the table by doing this
hot = new Handsontable(container, {option})
and then you can use the method like hot.loadData() etc..
To use handsontable with vuejs, there a wrapper we can find here https://github.com/handsontable/vue-handsontable-official. With the wrapper you make a table like this :
<template>
<div id="hot-preview">
<HotTable :root="root" :settings="hotSettings"></HotTable>
</div>
</template>
<script>
import HotTable from 'vue-handsontable-official';
import Vue from 'vue';
export default {
data: function() {
return {
root: 'test-hot',
hotSettings: {
data: [['sample', 'data']],
colHeaders: true
}
};
},
components: {
HotTable
}
mounted () {
localforage.config({
driver: localforage.INDEXEDDB,
name: 'matchlist-database'
})
localforage.getItem('DB').then(function (value) {
console.log('then i fetch the DB: ' + JSON.stringify(value))
if (value !== 'null') {
console.log('dB contain something')
**root**.loadData(value)
}
</script>
So it work fine when i give an array but to load the data from a DB you must call the handsontable method hot.loadData(data).
i cannot find how to call this method in vuejs i always get the error
TypeError: root.loadData is not a function
i tried with all i could think of instead of root ex: HotTable.loadData(value)
but to no avail
Can someone point me out how i would call handsontable methods from the vuejs wrapper. Or point me out what kind of reading i should do to understand my mistake. Thank a lot
There are two problems here, not bad ones :)
1st problem:
If you want to refer to your data inside Vue's methods/computed properties/watchers/lifecycle events, you should use the this keyword. If you have data: function() { return { root: "root-value" }} and you would like to console.log that "root-value" string, you should write console.log(this.root) inside your mounted handler.
If you had something like:
data: function() {
return {
hot = new Handsontable(container, {option})
....
};
You could call hot.loadData() like so:
mounted() {
this.hot.loadData();
...
}
So this refers to the Vue instance which exposes your data properties.
2nd problem:
If I understand the component wrapper correctly, you are supposed to pass data to it as props, not call any Handsontable methods directly.
<HotTable :root="root" :settings="hotSettings"></HotTable>
This means that Vue passes whatever you have as root in your data to the HotTable component. It also passes whatever you have as settings in your data. In the example, HotTable receives these:
root: 'test-hot',
hotSettings: {
data: [['sample', 'data']],
colHeaders: true
}
Now if you want to change/update/modify/add data that should be passed to the HotTable component, you should update your data in the Vue instance. You should do something like this.hotSettings = something new and this.root = something else and the HotTable component would receive those.
To understand what's really happnening with the HotTable, read all of the component documentation. Really. You will save lots of time if you read through the documentation. It all makes sense after that!
https://v2.vuejs.org/v2/guide/components.html