Vue3 Check if a Slot is Empty - vue.js

Is there a Vue3 equivalent to the following Vue2 method:
methods: {
hasSlot(name) {
return !!this.$slots[name]
}
}
in Vue3's Composition API?
I've tried:
setup({slots}) {
const hasSlot = (name) => {
return !!slots[name];
}
return { hasSlot }
}
but it isn't returning the expected value as slots is undefined (per error out in console).

As pointed out in comments, setup()'s second argument (the context) contains the component's slots. The first argument is for the component's props.
export default {
setup(props, { slots }) {
const hasSlot = name => !!slots[name]
return { hasSlot }
}
}
demo 1
The slots are also exposed in the template as $slots, so you could replace hasSlot(slotName) with $slots[slotName] or just $slots.SLOTNAME (e.g., $slots.footer):
<template>
<footer v-if="$slots.footer">
<h3>footer heading</h3>
<slot name="footer" />
</footer>
</template>
demo 2

Now, in Vue3 composition API , you can use useSlots.
<script setup>
const slots = useSlots()
const hasSlot = (name) => {
return !!slots[name];
}
</script>

Related

Vue3 composable with render

Is it possible to create a composable function that would use render function so it can display something?
Example:
import { h } from 'vue'
export function useErrorHandling() {
return {
render() {
return h('div', { class: 'bar', innerHTML: 'world!' })
}
}
}
<script setup>
import { useErrorHandling } from './mouse.js'
useErrorHandling()
</script>
<template>
hello
</template>
plaground with above example
Yes It is possible to do that you need just store the value returned by the composable in a variable and use it as a component
const err = useErrorHandling()
//in template
// <err />
Playground Example

Is there a way to share reactive data between random components in Vue 3 Composition API?

Having some reactive const in "Component A," which may update after some user action, how could this data be imported into another component?
For example:
const MyComponent = {
import { computed, ref } from "vue";
setup() {
name: "Component A",
setup() {
const foo = ref(null);
const updateFoo = computed(() => foo.value = "bar");
return { foo }
}
}
}
Could the updated value of 'foo' be used in another Component without using provide/inject?
I am pretty new in the Vue ecosystem; kind apologies if this is something obvious that I am missing here.
One of the best things about composition API is that we can create reusable logic and use that all across the App. You create a composable functions in which you can create the logic and then import that into the components where you want to use it. Not only does this make your component much cleaner but also your APP much more maintainable. Below is a simple example of counter to show how they can be used. You can find working demo here:
Create a composable function for counter:
import { ref, computed } from "vue";
const counter = ref(0);
export const getCounter = () => {
const incrementCounter = () => counter.value++;
const decrementCounter = () => counter.value--;
const counterPositiveOrNegitive = computed(() =>
counter.value >= 0 ? " Positive" : "Negitive"
);
return {
counter,
incrementCounter,
decrementCounter,
counterPositiveOrNegitive
};
};
Then you can import this function into your components and get the function or you want to use. Component to increment counter.
<template>
<div class="hello">
<h1>Component To Increment Counter</h1>
<button #click="incrementCounter">Increment</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "IncrementCounter",
setup() {
const { incrementCounter } = getCounter();
return { incrementCounter };
},
};
</script>
Component to decrement counter:
<template>
<div class="hello">
<h1>Component To Decrement Counter</h1>
<button #click="decrementCounter">Decrement</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "DecrementCounter",
setup() {
const { decrementCounter } = getCounter();
return { decrementCounter };
},
};
</script>
Then in the main component, you can show the counter value.
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div class="counters">
<IncrementCounter />
<DecrementCounter />
</div>
<h3>Main Component </h3>
<p>Counter: {{ counter }}</p>
<p>{{ counterPositiveOrNegitive }}</p>
</template>
<script>
import IncrementCounter from "./components/IncrementCounter.vue";
import DecrementCounter from "./components/DecrementCounter.vue";
import { getCounter } from "./composables/counterExample";
export default {
name: "App",
components: {
IncrementCounter: IncrementCounter,
DecrementCounter: DecrementCounter,
},
setup() {
const { counter, counterPositiveOrNegitive } = getCounter();
return { counter, counterPositiveOrNegitive };
},
};
Hope this was somewhat helpful. You can find a working example here:
https://codesandbox.io/s/vue3-composition-api-blfpj

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>

How to access 'layout' or 'page' function directly in component?

How can I access a layout- or page-function directly in a component? Is there a special variable like $root or $parent?
I found a way to do this, but it seems dirty. I saw the component structure using Vue DevTool, and I found the layout is the root's child, so I called the layout's function like this:
this.$root.$children[2].getMap()
Is there a cleaner way?
You could use Vue's provide/inject feature for this. For instance, a page/layout could provide the getMap():
<template>
<MapButton />
</template>
<script>
export default {
name: 'MapPage',
provide() {
return {
getMap() {
return { /* map */ }
}
}
}
}
</script>
...and then any child component on that page/layout could inject the method where needed:
<template>
<button #click="getMap">Get map</button>
</template>
<script>
export default {
name: 'MapButton',
inject: {
getMap: {
default() {
return () => {
console.log('no map')
}
}
}
},
mounted() {
console.log('map', this.getMap())
}
}
</script>

How to render children into specified named slot in VueJs?

Refer to the code below, currently all the children were rendered inside the default slot even though the slot name is given.
Not sure whether vue createElement function supports named slot?
#Component({
props:[]
})
export class TestComponent extends Widget{
items:any[];
render(h:any){
const rootcmp = {
template:`<div>
Temp:<slot name="temp"></slot>
Default:<slot></slot>
</div>`
, data:()=>{
return {};
}
}
const cmp = {
template:'<div slot="default">This is child</div>'
, data:()=>{
return {};
}
}
const cmp2 = {
template:'<div slot="temp">This is child</div>'
, data:()=>{
return {};
}
}
return h(rootcmp, [h(cmp), h(cmp2)]);
}
}
Current behavior:
<div>
Temp:Default:
<div>This is child</div>
<div>This is child</div>
</div>
Expected behavior:
<div>
Temp:
<div>This is child</div>
Default:
<div>This is child</div>
</div>
It sure does, in your options object, try {slot:'slot-name'}