why are the values of props recognised despite `props` not being used - vuejs3

I have a very simple vuejs component like this :
<template>
<h1>{{title}}</h1>
</template>
<script setup>
const props = defineProps({
title: String,
})
</script>
title is correctly interpreted to the prop being passed to this component. Out of curiosity: why do I not need to do {{props.title}} for this to work.
This only reason I now write {{props.title}} is in order to do away with lint errors.

Related

Vue - using props on custom elements fails using createApp and mount?

We would like to pass props to custom elements that uses createApp
// index.html
<div id="my-root">
<my-element prop1="abc"></my-element>
</div>
// my-element.vue
<script lang="ts" setup>
const props = defineProps<{ prop1: number }>();
</script>
<template>
{{props.prop1}}
</template>
This works fine, but as our custome element get bigger we would like to register components and use e.g pinia and other tools. Do use those we need to add createApp and mount it. But then prop1 is always undefined
// main.ts
import ...<lots of imports>
import AppCe from "./AppWebComponent.ce.vue";
import { createPinia } from "pinia";
// Adding code below is causing prop1 to be undefined - if we skip this part, prop1 works fine
const pinia = createPinia();
const app = createApp(App);
app.use(pinia).use(ConfirmDialog);
app.component(...<lots of components>);
app.mount("#my-root");
const ceApp = defineCustomElement(AppCe);
customElements.define("my-element", ceApp);
update:
Here's a sample without: https://stackblitz.com/edit/vue3-script-setup-with-vite-56rizn?file=src/my-element/my-element-main.js
And here's a sample with the createApp: https://stackblitz.com/edit/vue3-script-setup-with-vite-gtkbaq?file=index.html
Any idea on how we could solve this?
We have a fallback, that is to do a getElementById and read the attribute value in the mounted callback - but that is not an optimal solution.
Thanks for any ideas!
update2:
Here's an attempt using #duannex suggestion. We're getting closer, the app is availible, components registered, but still no sigar. : https://stackblitz.com/edit/vue3-script-setup-with-vite-ofwcjt?file=src/my-element/defineCustomElementWrapped.js
Based on update2 with the wrapped defineCustomElement; Just pass the props to the render function:
render() {
return h(component, this.$props)
},
https://stackblitz.com/edit/vue3-script-setup-with-vite-vfdnvg?file=src/my-element/defineCustomElementWrapped.js

Multiple conflicting contents for sourcemap source error when using client-only

I have a component that uses windows so I rendered it with client-only like:
<template>
<h2>Testing Component2</h2>
<client-only>
<Component2></Component2>
</client-only>
</template>
<script lang="ts">
import Component2 from './component-2'
export default defineComponent({
components: {
Component2
},
setup() {}
})
</script>
this works locally, the problem is that when I try to build to deploy, it always returns the error:
Multiple conflicting contents for sourcemap source error ./src/pages/index.vue
Is there a way to fix this or am I doing something wrong?
I used
LazyClientOnly
and there add a lazy import for the component like: Component2: () => import('./component-2') and works perfectly

Vue3 Parent component call child component method

I can't call child component method in parent component in Vue3
In Vue2, I can call child component method like this
this.$root.$refs.ChildComponent.methodName()
But in Vue3, I receive a error like this
runtime-core.esm-bundler.js:218 Uncaught TypeError: Cannot read properties of undefined (reading 'methodName')
defineExpose could do the magic. You could do something like this:
// in Parent
<template>
<ChildComponent ref="myChild"/>
</template>
<script>
const myChild = ref(null);
function() {
myChild.childMethod();
}
</script>
// ChildComponent
<template> ... </template>
<script setup>
function childMethod() {
// do something
}
defineExpose({
childMethod
});
</script>
You might want to pass in a prop to the child, and react to a change-event by calling the method. This could look something like this:
<!-- Parent.vue -->
<script setup>
/* declare invokeChildMethod */
</script>
<template>
<Child :prop="invokeChildMethod" />
</template>
As can be seen in the code below, when the variable (here called invokeChildMethod) changes (in the parent), an event for the child will be fired.
Here's a resource on watching props in Vue3.
In addition to nocrash9000's answer (which did the trick for me), do not forget to add the import statement
import { defineExpose } from 'vue'

Problem creating a WebComponent from a SFC with Vue 3.2.9

I am trying to create a web component in Vue3. For this I use the Vue cli with the target library. Everything works as expected.
The problem is that I can't get any values from the props parameter of the setup function.
If I use the component as a Vue component the props work. As a web component i cant get any value from the props parameter
Its seams that the property wc-Test is not forwarded properly.
Does anyone have any ideas?
The Component Code
<template>
<a class="btn">
<slot></slot>
</a>
</template>
<script>
import {version } from "vue";
export default {
name: 'xn-button',
props: {
variant: {
default: 'normal',
type: String
}
},
setup(props, context) {
console.log(`Vue: ${version}`)
console.log(props)
console.log(props.variant)
},
}
</script>
<style></style>
Usage as Part of Vue Library:
<xn-button variant="Vue-Test">Test1</xn-button>
Usage as WebComponent:
<xn-button variant="wc-Test">Test2</xn-button>
Console output:
image

How to pass props to slot, Vue 3

Making a custom select box component. But having some trouble when I try to pass to slot.
<vb-select v-model="container"
title="bla bla"
multiple>
<vb-option v-for="(item, idx) in items" :key="idx" :value="item">{{item}}</vb-option>
</vb-select>
vb-option is slot and I am calling it in vb-select component.
vb-select
<ul v-if="state" class="vb-options">
<slot :state="state" :multiple="multiple"></slot>
</ul>
When I try to pass multiple to slot as a prop. I can't listen/watch it in the vb-option
vb-option
<li class="vb-option">
{{multiple}}
</li>
props:{
multiple:Boolean,
},
What is right way to achieve this? Watching props inside the slot for changes.
<slot :state="state" :multiple="multiple"></slot>
What you've done here is use scoped slots - but they don't quite work the way you're trying to use them.
Try this way:
<vb-select>
<template #default="scope"> <!-- you can also do #default="{ state, multiple }" -->
<vb-option :multiple="scope.multiple" />
</template>
</vb-select>
Slot scope isn't automatically applied to the props of the component you put inside the slot - you need to pass it explicitly as shown above.
So that's the "how slot scope works" part, but the second part is that you're trying to implement something called "compound components", and it's a little trickier than that in Vue. In React, to do this you'd use React Context, but in Vue you'd use provide/inject.
Here's a repo where I outline some usecases, including an select/option:
https://github.com/sethidden/vue-compound-components
If that's too crazy, there's really nothing wrong with doing something like this and handling option rendering in vb-select itself:
<vb-select :options="[{ value: '123', label: 'Hello' }, { value: '567', label: 'Helloooo' }]" />
Pass hasError to default slot.
<script setup>
const slots = useSlots():
const Child = defineComponent({
render: () => {
const data = slots.default?.()[0];
data!.props!.hasError = true;
return data;
},
});
</script>
<template>
<Child />
</template>

Resources