lospoy
lospoy

Reputation: 1

Vue3: re-render component - composition API

I have a navbar component and a view component. When a user logs in, I want both components to re-render. Currently only the view component re-renders, the navbar doesn't.

Tried force re-render with :key passing a "user" object, passing a "navKey" counter that updates on changes. Regardless, I always get "[Vue warn]: Invalid watch source: true A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types."

Read dozens of guides and docs and spent days on this but I just don't get what I'm missing. First app, appreciate your help.

App.vue

    <template>
      <div v-if="appReady" class="min-h-full font-Poppins box-border">
        <Navigation :key="state" />
        <router-view />
      </div>
    </template>
    
    <script>
    import Navigation from "./components/Navigation.vue";
    import { ref, reactive } from "vue";
    import store from "./store/store.js";
    import { useRouter } from "vue-router";
    
    export default {
      components: {
        Navigation,
      },
    
      setup() {
        // Data & variables
        const appReady = ref(null);
        const router = useRouter()
    
        const user = JSON.parse(localStorage.getItem("BJJFocusUser"))
        const state = store.methods.setUser(user)
    
        if (!user) {
          appReady.value = true;
          console.log("No user logged in");
          router.push({ name: "Login" });
        } else {
          store.methods.setUser(user);
          appReady.value = true;
          console.log("User logged in");
          router.push({ name: "ProgressView" });
        }
    
        return { appReady, user, state };
      },
    };
    </script>

Navigation.vue

    <template>
      <header class="bg-at-light-orange text-white">
        <nav
          class="container py-5 px-4 flex flex-column gap-4 items-center sm:flex-row"
        >
          <div class="flex items-center gap-x-4">
            <img
              class="w-32"
              src="../assets/vector/default-monochrome-white.svg"
              alt="bjj focus logo"
            />
          </div>
          <Slide
          </Slide>
        </nav>
      </header>
    </template>
    
    <script>
    import { logoutUser } from "../services/userService";
    import { useRouter } from "vue-router";
    import { Slide } from "vue3-burger-menu"
    import store from "../store/store"
    
    export default {
        components: {
            Slide
        },
    
        setup() {
          const router = useRouter();
          const user = store.state.user
    
          // Logout function
          const logout = async () => {
            logoutUser();
            store.methods.setUser()
            router.push({ name: "Login" });
          };    
          return { logout, Slide, store, user };
        },
    
    };
    </script>

Store.js

    import { reactive } from "vue";
    
    const state = reactive({
      user: null,
    });
    
    const methods = {
      setUser(payload) {
        state.user = payload ? payload : null;
      },
      getUser() {
        return JSON.stringify(state.user)
      }
    };
    
    export default {
      state,
      methods,
    };

Upvotes: 0

Views: 1698

Answers (1)

lospoy
lospoy

Reputation: 1

The way I managed to make the navbar re-render was:

  1. Adding an EventBus (emitter) to the Login view - you need to make this fire somewhere using emitLogin()
// Emitter (EventBus) this section emits an event that can be listened to globally
const emitter = inject('emitter')
const emitLogin = _ => {
    emitter.emit('userHasLoggedIn', true)
}
  1. Adding another EventBus (emitter) to the Logout (in my case it's in the Navigation bar) - you need to make this fire somewhere using emitLogout()
// Emitter (EventBus) this section emits an event that can be listened to globally
const emitter = inject('emitter')
const emitLogout = _ => {
    emitter.emit('userHasLoggedOut', true)
}
  1. Adding a listener to App.vue (root Vue component) AND a :key that reads a variable that in turn gets updated when the listener receives the emitter's signal
<template>
  <div v-if="appReady" class="min-h-full font-Poppins box-border bg-at-light-orange">
    <Navigation :key="navRerenderKey" />   // **NOTE THE :key HERE**
    <router-view />
  </div>
</template>

<script>
export default {
    setup() {
        // Initialize this variable before the functions
        const navRerenderKey = ref(0)

        // Listener (EventBus) this section listens to the emitters
        const emitter = inject('emitter')
        emitter.on('userHasLoggedIn', (value) => {
            navRerenderKey.value += 1
            console.log("(emitter) Logged In")
        })
        emitter.on('userHasLoggedOut', (value) => {
            navRerenderKey.value += 1
            console.log("(emitter) Logged Out")
        })
    }
}
</script>

Upvotes: 0

Related Questions