vue.jsvue-props

Vue - parent component needs to check the value of a prop in the child component before doing something (Conditional assignment)


i have this component, if nav is entered or leaved, i want to reassign isNavClosed to the opposite of its value so nav and main content get decreased and increased accordingly, but i only want to do that if hamburger menu is closed, in nav component i have ref isHamburger to express the state of hamburger menu, so in enterLeaveNav function i want to reassign isNavClosed ref only if ref isHamburger is true, therefore i need to check its value from nav child before reassigning it, how can i access the value of isHamburger from parent without actually moving it to the parent ?

<script setup>
import Header from "@/Components/Header.vue";
import Nav from "@/Components/Nav.vue";
import { ref } from "vue";

const isScroll = ref(false);
const isNavClosed = ref(false);

function setIsNavClosed() {
    isNavClosed.value = !isNavClosed.value;
}

function enterLeaveNav() {
    isScroll.value = !isScroll.value;
}
</script>


<template>
    <div>

        <!-- Page Heading -->

        <header class="header container">
            <Header />
        </header>
        <div class="container-fluid">
            <div class="row g-0">

        <!-- navigation component -->

                <nav
                    class="p-0 nav"
                    :class="[
                        isScroll ? 'overflowScroll' : 'overflowHidden',
                        isNavClosed ? 'width833' : 'width1633',
                    ]"
                    @mouseenter="enterLeaveNav"
                    @mouseleave="enterLeaveNav"
                >
                    <Nav @open-close-nav="setIsNavClosed" />
                </nav>

        <!-- Main content-->

                <main
                    class="ps-5"
                    :class="[
                        isNavClosed
                            ? 'offset-md-1 col-md-10'
                            : 'offset-md-2 col-md-9',
                    ]"
                    style="margin-top: 100px"
                >
                    <slot name="content" />
                </main>
            </div>
        </div>
    </div>
</template>

i tried to use composable to use these states accross child and parent component, but the problem is that when nav component mutate state, only nav component will be updated, but the parent will not be updated and the state will always be false:

Composables/useNavigation.js

import { ref } from "vue";

export function Navigation() {
    const isNavClosed = ref(false);
    const isHamburgerClosed = ref(false);
    const isScroll = ref(false);

    function toggleNav() {
        isNavClosed.value = !isNavClosed.value;
        isHamburgerClosed.value = !isHamburgerClosed.value;
    }

    function enterLeaveNav() {
        if (isHamburgerClosed.value) {
            isNavClosed.value = !isNavClosed.value;
        }
        isScroll.value = !isScroll.value;
    }

    return {
        isScroll,
        isNavClosed,
        isHamburgerClosed,
        toggleNav,
        enterLeaveNav,
    };
}

Nav.vue

    <div
        class="hamburgerMenu"
        :class="isHamburgerClosed ? 
        'hamburgerMenuMarginLeft2' :
        'hamburgerMenuMarginLeft80'"
        @click="toggleNav">
            <div class="hamburgerPiece"></div>
            <div class="hamburgerPiece"></div>
            <div class="hamburgerPiece"></div>
    </div>

parent component

<script setup>
import Header from "@/Components/Header.vue";
import Nav from "@/Components/Nav.vue";
import { Navigation } from "@/Composables/Navigation";

const { isScroll, isHamburgerClosed, isNavClosed, enterLeaveNav } =
    Navigation();
</script>
<!-- @open-close-nav="setIsNavClosed" -->
<!-- :class="[isNavClosed ? 'offset-md-1' : 'offset-md-2']" -->

<template>
    <div>
        <!-- Page Heading -->
        <header class="header container">
            <Header />
        </header>
        <div class="container-fluid">
            <div class="row g-0">
                {{ isHamburgerClosed }}
                <nav
                    class="p-0 nav"
                    :class="[
                        isScroll ? 'overflowScroll' : 'overflowHidden',
                        isNavClosed ? 'width833' : 'width1633',
                    ]"
                    @mouseenter="enterLeaveNav"
                    @mouseleave="enterLeaveNav"
                >
                    <Nav />
                </nav>
                <main
                    class="ps-5"
                    :class="[
                        isNavClosed
                            ? 'offset-md-1 col-md-10'
                            : 'offset-md-2 col-md-9',
                    ]"
                    style="margin-top: 100px"
                >
                    <slot name="content" />
                </main>
            </div>
        </div>
    </div>
</template>


Solution

  • Since you parent/child components aren't intended to use separately you probably should use provide/inject with the state in the parent component. I love it and use it always in parent-child components that are used only together:

    https://vuejs.org/guide/components/provide-inject.html

    Also note that the global state is for business data. The UI state (expanded / collapsed) that lives only when its components are alive should be stored in the root component of a component hierarchy.