vue.js vue-gl missing props camera in component - vue.js

I installed Vue-gl in my app.js as
const { VglRenderer, VglScene } = require('vue-gl');
Object.keys(VueGL).forEach((name) => Vue.component(name, VueGL[name]));
Then in my parent component I have a child component:
<scene :positionItem="positionItem"></scene>
import scene from "./../components/scene";
Having this code:
export default {
name: "configurator",
components :{
scene
},
data: function(){
return {
positionItem: -4,
}
},
methods:{
updatePosition(){
this.$root.$emit("input", {
positionItem :this.$refs.positionItem.value
})
}
},
mounted() {
this.$root.$on('input', data => {
this.positionItem = data;
});
}
}
The child component "scene" is
<template>
<vgl-renderer >
<vgl-sphere-geometry name="sphere"></vgl-sphere-geometry>
<vgl-scene name="main">
<!-- Re-rendering will be triggered when zCoordinate changes. -->
<vgl-mesh :position="`0 0 ${positionItem}`" geometry="sphere"></vgl-mesh>
</vgl-scene>
<vgl-perspective-camera orbit-position="12 0 0" name="camera"></vgl-perspective-camera>
</vgl-renderer>
<script lang="ts">
export default {
props: ["positionItem"],
data: function () {
return {
}
},
}
</script>
The renders works but I got 2 errors about missing props camera and scene:
vue.esm.js?efeb:628 [Vue warn]: Missing required prop: "camera"
found in
--->
at src/components/scene.vue
at src/components/configurator.vue
at src/App.vue
How to fix it?

I had the exact same problem. The vgl-renderer component needs to be given a name and a screne property of type string. These components must be present in the scene with the same names. Otherwise vue-gl will throw another error. I can't find anything about these required properties on the vue-gl documentation. Maybe it's outdated. I managed to get it working like this ...
<template>
<vgl-renderer
camera= "camera"
scene= "scene"
class="getting-started"
>
<vgl-box-geometry name="box"></vgl-box-geometry>
<vgl-scene name="scene">
<vgl-mesh geometry="box"></vgl-mesh>
</vgl-scene>
<vgl-perspective-camera name="camera" orbit-position="3 1 0.5"></vgl-perspective-camera>
</vgl-renderer>
</template>
<script>
const { VglRenderer, VglScene, VglBoxGeometry, VglMesh, VglPerspectiveCamera } = require('vue-gl');
export default {
components : {
VglRenderer, VglScene, VglBoxGeometry, VglMesh, VglPerspectiveCamera
}
}
</script>

Related

VueJS 2.x Child-Component doesn't react to changed parent-property

I have the problem, that a component doesn't recognize the change of a property.
The component is nested about 5 levels deep. Every component above the faulty one does update with the same mechanics and flawlessly.
I invested some time to get to the problem, but I can't find it.
The flow is:
Dashboard (change value and pass as prop)
TicketPreview (Usage and
pass prop)
CommentSection (Pass prop)
CommentList (FAULTY / Usage of prop)
Everything down to the commentSection is being updated as expected, but the commentList doesn't get the update notification (beforeUpdate doesn't get triggered).
Since I tested quite a few things I will only post the essential code from commentSection (parent) and commenList (child)
DISCLAIMER: This is a prototype code without backend, therefore typical API-Requests are solved with the localStorage of the users browser.
commentSection
<template>
<div id="comment-section">
<p>{{selectedTicket.title}}</p>
<comment-form :selectedTicket="selectedTicket" />
<comment-list :selectedTicket="selectedTicket" />
</div>
</template>
<script>
import CommentForm from "#/components/comment-section/CommentForm";
import CommentList from "#/components/comment-section/CommentList";
export default {
name: "CommentSection",
components: {
CommentForm,
CommentList,
},
props: {
selectedTicket: Object,
},
beforeUpdate() {
console.log("Comment Section");
console.log(this.selectedTicket);
},
updated() {
console.log("Comment Section is updated");
}
}
</script>
CommentList
<template>
<div id="comment-list">
<comment-item
v-for="comment in comments"
:key="comment.id"
:comment="comment"
/>
</div>
</template>
<script>
import CommentItem from "#/components/comment-section/CommentItem";
export default {
name: "CommentList",
components: {
CommentItem,
},
data() {
return {
comments: Array,
}
},
props: {
selectedTicket: Object,
},
methods: {
getComments() {
let comments = JSON.parse(window.localStorage.getItem("comments"));
let filteredComments = [];
for(let i = 0; i < comments.length; i++){
if (comments[i].ticketId === this.selectedTicket.id){
filteredComments.push(comments[i]);
}
}
this.comments = filteredComments;
}
},
beforeUpdate() {
console.log("CommentList");
console.log(this.selectedTicket);
this.getComments();
},
mounted() {
this.$root.$on("updateComments", () => {
this.getComments();
});
console.log("CL Mounted");
},
}
</script>
The beforeUpdate() and updated() hooks from the commentList component are not being fired.
I guess I could work around it with an event passing the data, but for the sake of understanding, let's pretend it's not a viable option right now.
It would be better to use a watcher, this will be more simple.
Instead of method to set comments by filtering you can use computed property which is reactive and no need to watch for props updates.
CommentSection
<template>
<div id="comment-section">
<p>{{ selectedTicket.title }}</p>
<comment-form :selectedTicket="selectedTicket" />
<comment-list :selectedTicket="selectedTicket" />
</div>
</template>
<script>
import CommentForm from "#/components/comment-section/CommentForm";
import CommentList from "#/components/comment-section/CommentList";
export default {
name: "CommentSection",
components: {
CommentForm,
CommentList
},
props: {
selectedTicket: Object
},
methods: {
updateTicket() {
console.log("Comment section is updated");
console.log(this.selectedTicket);
}
},
watch: {
selectedTicket: {
immediate: true,
handler: "updateTicket"
}
}
};
</script>
CommentList
<template>
<div id="comment-list">
<comment-item
v-for="comment in comments"
:key="comment.id"
:comment="comment"
/>
</div>
</template>
<script>
import CommentItem from "#/components/comment-section/CommentItem";
export default {
name: "CommentList",
components: {
CommentItem
},
props: {
selectedTicket: Object
},
computed: {
comments() {
let comments = JSON.parse(window.localStorage.getItem("comments"));
let filteredComments = [];
for (let comment of comments) {
if (comment.ticketId == this.selectedTicket.id) {
filteredComments.push(comment);
}
}
// // using es6 Array.filter()
// let filteredComments = comments.filter(
// (comment) => comment.ticketId == this.selectedTicket.id
// );
return filteredComments;
}
}
};
</script>
I found the problem: Since commentList is only a wrapper that doesn't use any of the values from the prop, the hooks for beforeUpdate and updated are never triggered. The Vue Instance Chart is misleading in that regard. The diagram shows it like beforeUpdate would ALWAYS fire, when the data changed (then re-render, then updated), but beforeUpdate only fires if the Component and Parent has to be re-rendered.
The Object updates as expected, it just never triggered a re-render on the child component because the wrapper has not been re-rendered.

vue3 performance warning using ref

vue is throwing this message:
Vue received a Component which was made a reactive object. This can
lead to unnecessary performance overhead, and should be avoided by
marking the component with markRaw or using shallowRef instead of
ref.
<template>
<component v-for="(el, idx) in elements" :key="idx" :data="el" :is="el.component" />
</template>
setup() {
const { getters } = useStore()
const elements = ref([])
onMounted(() => {
fetchData().then((response) => {
elements.value = parseData(response)
})
})
return { parseData }
}
is there a better way to do this?
First, you should return { elements } instead of parseData in your setup i think.
I solved this issue by marking the objects as shallowRef :
import { shallowRef, ref, computed } from 'vue'
import { EditProfileForm, EditLocationForm, EditPasswordForm} from '#/components/profile/forms'
const profile = shallowRef(EditProfileForm)
const location = shallowRef(EditLocationForm)
const password = shallowRef(EditPasswordForm)
const forms = [profile, location, password]
<component v-for="(form, i) in forms" :key="i" :is="form" />
So you should shallowRef your components inside your parseData function. I tried markRaw at start, but it made the component non-reactive. Here it works perfectly.
you could manually shallowCopy the result
<component v-for="(el, idx) in elements" :key="idx" :data="el" :is="{...el.component}" />
I had the same error. I solved it with markRaw. You can read about it here!
my code :
import { markRaw } from "vue";
import Component from "./components/Component.vue";
data() {
return {
Component: markRaw(Component),
}
For me, I had defined a map in the data section.
<script>
import TheFoo from '#/TheFoo.vue';
export default {
name: 'MyComponent',
data: function () {
return {
someMap: {
key: TheFoo
}
};
}
};
</script>
The data section can be updated reactively, so I got the console errors. Moving the map to a computed fixed it.
<script>
import TheFoo from '#/TheFoo.vue';
export default {
name: 'MyComponent',
computed: {
someMap: function () {
return {
key: TheFoo
};
}
}
};
</script>
I had this warning while displaying an SVG component; from what I deduced, Vue was showing the warning because it assumes the component is reactive and in some cases the reactive object can be huge causing performance issues.
The markRaw API tells Vue not to bother about reactivity on the component, like so - markRaw(<Your-Component> or regular object)
I also meet this problem today,and here is my solution to solve it:
setup() {
const routineLayoutOption = reactive({
board: {
component: () => RoutineBoard,
},
table: {
component: () => RoutineTable,
},
flow: {
component: () => RoutineFlow,
},
});
}
I set the component variant as the result of the function.
And in the ,bind it like compoennt()
<component
:is="routineLayoutOption[currentLayout].component()"
></component>

Element UI dialog component can open for the first time, but it can't open for the second time

I'm building web app with Vue, Nuxt, and Element UI.
I have a problem with the Element dialog component.
It can open for the first time, but it can't open for the second time.
This is the GIF about my problem.
https://gyazo.com/dfca3db76c75dceddccade632feb808f
This is my code.
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first :visible=visible></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
ModalFirst.vue
<template>
<el-dialog
title="Tips"
:visible.sync="visible"
width="30%"
>
<span>This is a message</span>
<span slot="footer" class="dialog-footer">
<a>Hello</a>
</span>
</el-dialog>
</template>
<script>
export default {
props: [ 'visible' ]
}
</script>
And I can see a warning message on google chrome console after closing the dialog.
The warning message is below.
webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:620 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "visible"
found in
---> <ModalFirst> at components/ModalFirst.vue
<Pages/index.vue> at pages/index.vue
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
This is the screenshot of the warning message.
https://gyazo.com/83c5f7c5a8e4d6816c35b3116c80db0d
In vue , using directly to prop value is not allowed . Especially when your child component will update that prop value , in my option if prop will be use
for display only using directly is not a problem .
In your code , .sync will update syncronously update data so I recommend to create local data.
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'visible' ],
data: function () {
return {
localVisible: this.visible // create local data using prop value
}
}
}
</script>
If you need the parent visible property to be updated, you can create your component to leverage v-model:
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'value' ],
data() {
return {
localVisible: null
}
},
created() {
this.localVisible = this.value;
this.$watch('localVisible', (value, oldValue) => {
if(value !== oldValue) { // Optional
this.$emit('input', value); // Required
}
});
}
}
</script>
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first v-model="visible"></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
v-model is basically a shorthand for :value and #input
https://v2.vuejs.org/v2/guide/forms.html#Basic-Usage
Side-note:
You can also import your component like so:
components: { ModalFirst },
as ModalFirst will be interpreted as modal-first as well by Vue.js

Why this.$listeners is undefined in Vue JS?

Vue.js version: 2.4.2
Below component always print this.$listeners as undefined.
module.exports = {
template: `<h1>My Component</h1>`,
mounted() {
alert(this.$listeners);
}
}
I register the component and put it inside a parent component.
Can someone tell me why?
You have to understand what $listeners are.
this.$listeners will be populated once there are components that listen to events that your components is emitting.
let's assume 2 components:
child.vue - emits an event each time something is written to input field.
<template>
<input #input="emitEvent">
</input>
</template>
<script>
export default {
methods: {
emitEvent() {
this.$emit('important-event')
console.log(this.$listeners)
}
}
}
</script>
parent.vue - listen to the events from child component.
<template>
<div class="foo">
<child #important-event="doSomething"></child>
</div>
</template>
<script>
import child from './child.vue'
export default {
data() {
return {
newcomment: {
post_id: 'this is default value'
}
}
},
components: { child },
methods: {
doSomething() {
// do something
}
}
}
</script>
With this setup, when you type something to the input field, this object should be written to the console:
{
`important-event`: function () { // some native vue.js code}
}
I added the following alias to my webpack.config.js file and this resolved the issue for me:-
resolve: {
alias: {
'vue$': path.resolve(__dirname, 'node_modules/vue/dist/vue.js')
}
},

Vue: How to use store with component?

//store
export default {
state: {
aboutModels: []
},
actions: {
findBy: ({commit}, about)=> {
//do getModels
var aboutModels = [{name: 'About'}] //Vue.resource('/abouts').get(about)
commit('setModels', aboutModels)
}
},
getters: {
getModels(state){
return state.aboutModels
}
},
mutations: {
setModels: (state, aboutModels)=> {
state.aboutModels = aboutModels
}
}
}
//component
import {mapActions, mapGetters} from "vuex";
export default {
name: 'About',
template: require('./about.template'),
style: require('./about.style'),
created () {
document.title = 'About'
this.findBy()
},
computed: mapGetters({
abouts: 'getModels'
}),
methods: mapActions({
findBy: 'findBy'
})
}
//view
<div class="about" v-for="about in abouts">{{about.name}}</div>
//error
vue.js:2532[Vue warn]: Cannot use v-for on stateful component root element because it renders multiple elements:
<div class="about" v-for="about in abouts">{{about.name}}</div>
vue.js:2532[Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node. (found in component <About>)
You are mapping your Vuex state getters and action correctly. Your problem is something else as your error message states...
In your component template you can not use v-for directive on a root element. For example this is not allowed because your component can have multiple root elements:
<template>
<div class="about" v-for="about in abouts">{{about.name}}</div>
</template>
instead do it this way:
<template>
<div>
<div class="about" v-for="about in abouts">{{about.name}}</div>
</div>
</template>
** *fixed typo in template tag **