Mostafa Said
Mostafa Said

Reputation: 1015

How to show/hide @foreach statement

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:

dropdown

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:

foreach2

-- 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

Answers (3)

Mostafa Said
Mostafa Said

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

Kamal Pandey
Kamal Pandey

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>

Demo

---- 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

Plastic
Plastic

Reputation: 10328

Try with a @if directive:

Conditional Rendering

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:

Lifecycle

Upvotes: 0

Related Questions