Reputation: 1015
I have a Vuejs 3 dropdown reusable component. My problem is that the @foreach statement runs before the component loads so it causes a flash of the foreach results which is very ugly upon refresh or when the page is loading.
To demonstrate please check this gif:
My component in blade:
<Dropdown title="{{ isset($currentCategory) ? ucwords($currentCategory->name) : 'Categories' }}">
<Dropdowncontent>
<Dropdownitems href="/">
All
</Dropdownitems>
<div>
@foreach ($categories as $category)
<Dropdownitems
href="/?category={{ $category->slug }}&{{ http_build_query(request()->except('category')) }}"
class="{{ isset($currentCategory) && $currentCategory->is($category) ? ' selectedCategoryItem' : '' }}">
{{ $category->name }}
</Dropdownitems>
@endforeach
</div>
</Dropdowncontent>
</Dropdown>
I added a div to contain the @foreach statement but i don't know what to do from here. I don't want to use alpineJS as it will defeat the purpose of using Vue (I guess?).
I just need a way to only display this div or the @foreach statement if the component is fully loaded or if the button is pressed or something like that. Any ideas?
-- EDIT --
I tried to hide the links in my 'dropdownitems' vue component and set the default value to false. The links are now hidden but still the blade @foreach statement echoing out the results as text before the component is loaded:
<template>
<a v-if="showLinks" href="" class="demogard categoryItems">
<slot />
</a>
</template>
<script>
export default {
name: "Dropdownitems",
setup() {
const showLinks = false;
return {
showLinks,
};
},
};
</script>
<style></style>
Here is a gif to show the result of that:
-- EDIT --
Here is my dropdown component:
<template>
<div
class="relative"
v-click-outside="onClickOutside"
@click="showCategories"
>
<slot name="toggler">
<button
class="flex max-h-52 w-full overflow-auto py-2 pl-3 pr-9 text-sm font-semibold lg:inline-flex lg:w-32"
>
{{ title }}
</button>
</slot>
<slot />
</div>
</template>
<script>
import vClickOutside from "click-outside-vue3";
import { ref, onMounted, provide } from "vue";
export default {
name: "Dropdown",
props: ["title"],
directives: {
clickOutside: vClickOutside.directive,
},
setup() {
const sharedState = ref(false);
const showCategories = () => {
sharedState.value = !sharedState.value;
};
const onClickOutside = (event) => {
sharedState.value = false;
};
provide("sharedState", sharedState);
return {
sharedState,
showCategories,
onClickOutside,
};
},
};
</script>
<style></style>
Upvotes: 2
Views: 684
Reputation: 1015
It's impossible to load Vue before PHP because your webpage only displays when full PHP code is received from the server. Therefore, we're never able to stop PHP or HTML from flashing if we're using them inside a reusable Vue component.
The solution I made is simply passing the value of the foreach loop as a prop to the Vue component in order for it to be displayed from there, not from my blade file.
Here's my code in blade after passing the value of the category name as a prop to my Vue component.
<Dropdown title="{{ isset($currentCategory) ? ucwords($currentCategory->name) : 'Categories' }}">
<Dropdowncontent>
<Dropdownitems href="/" category="All"></Dropdownitems>
@foreach ($categories as $category)
<Dropdownitems
category="{{ $category->name }}"
href="/?category={{ $category->slug }}&{{ http_build_query(request()->except('category')) }}"
class="{{ isset($currentCategory) && $currentCategory->is($category) ? ' selectedCategoryItem' : '' }}">
</Dropdownitems>
@endforeach
</Dropdowncontent>
</Dropdown>
Here is me displaying it from there the Vue dropdown items component:
<template>
<a href="" class="demogard categoryItems">
<slot>{{ category }}</slot>
</a>
</template>
<script>
export default {
name: "Dropdownitems",
props: ["category"],
};
</script>
<style></style>
Upvotes: 0
Reputation: 119
As your question, I think you have to add if condition
on your dropdown component.
Your dropdown component should be like this
#dropdown.vue
<template>
<div class="dropdown">
<div @click="show = !show">{{title}}</div>
<div v-if="show">
<slot />
</div>
</div>
</template>
<script>
import { ref } from "vue";
export default {
props: ["title"],
setup(props) {
const show = ref(false);
return {
show,
};
},
};
</script>
---- EDIT ----
#dropdown.vue
<template>
<div
class="relative"
v-click-outside="sharedState = false"
>
<slot name="toggler">
<button
class="flex max-h-52 w-full overflow-auto py-2 pl-3 pr-9 text-sm font-semibold lg:inline-flex lg:w-32"
@click="sharedState = !sharedState"
>
{{ title }}
</button>
</slot>
<div v-if="sharedState">
<slot />
</div>
</div>
</template>
<script>
import vClickOutside from "click-outside-vue3";
import { ref, onMounted, provide } from "vue";
export default {
name: "Dropdown",
props: ["title"],
directives: {
clickOutside: vClickOutside.directive,
},
setup() {
const sharedState = ref(false);
// const showCategories = () => {
// sharedState.value = !sharedState.value;
// };
// const onClickOutside = (event) => {
// sharedState.value = false;
// };
provide("sharedState", sharedState);
return {
sharedState,
//showCategories,
//onClickOutside,
};
},
};
</script>
<style></style>
Upvotes: 1
Reputation: 10328
Try with a @if directive:
from the documentation:
<button @click="awesome = !awesome">Toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
As showed in the example it render the "h1" tag conditionally respect the "awesome" variable. In this case i will set a default value of "false" and i will turn it to "true" in the mounted hook:
Upvotes: 0