javascriptvue.jsvuejs3nuxt.jsnuxt3.js

UI elements jitter when being conditionally rendered


I'm have an issue where buttons and links jitter on state change (when clicking on the "Sign Out" button). I tried both v-if and v-show and the jitter persists. Video of the issue: https://www.veed.io/view/89672f51-f55c-411c-883f-440b02cfa4de?panel=share Reproduction: https://stackblitz.com/edit/nuxt-starter-a8hntf?file=app%2Fcomposables%2FuseStoreAuth.ts.

<div>
  <div>
      <button @click="setColorTheme()">
          <IconDarkMode />
      </button>
      <div v-if="!signedIn && !pending">
          <NuxtLink to="/sign-in">
              <span>Sign In</span>
              <IconSignIn />
          </NuxtLink>
      </div>
  </div>
  <div
      v-if="signedIn && !pending">
      <NuxtLink to="/profile">
          <span>Profile</span>
          <IconProfile />
      </NuxtLink>
      <button to="/sign-in" @click="signOut">
          <span>Sign Out</span>
          <IconSignOut />
      </button>
  </div>
</div>

I also have a 300ms delay for signedIn state in signOut function to wait until page transition ends, but jitter still persists.

if (res.ok) {
    const data = await res.json();
    state.successMessage = data.message;
    if (route.path === '/') {
        window.location.reload();
    } else {
        window.location.href = '/';
    }
    setTimeout(() => {
        state.signedIn = data.signedIn;
    }, 300);
}

Looking for a possible solution.


Solution

  • The solution is recommended by @Estus Flask in the comments above.

    Regarding the UI element jitter:

    It's "pending" condition that forces the layout to jump. It's not needed there if you need transitions, i.e. either one of these divs with v-ifs should exist in the layout an any moment of time.

    Regarding the buttons changing before page transition starts:

    You need to use separate state for UI that will differ from store.signedIn and use to promises for correct control flow. I.e onSignOutClick = async () => { await store.signOut(); uiSignedIn.value = store.signedIn }. Also, navigateTo and router methods return promises, you may want to await them too.