Search code examples
vue.jsvuejs3vue-router

Vue3 - router push to another page only renders top part of the page - not scrollable


I am experiencing an issue that I actually already have had for quite some time. My setup is Vue3 and Vuetify 3.

Whenever I change the page after some kind of calculation:

    router.push({ name: 'AnotherPage', params: { id: index, variable: x} });

The page is redirected to 'AnotherPage' but the page is not scrollable, so only the part of the page that fits on the page is rendered.

After doing an F5 refresh, the complete page is rendered and scrollable.

I only noticed this behavior when I was looking into redirecting to a certain section on a page, using anchors, and found that it was not working.

scrollToElement() {
  if (this.$route.hash) {
    const targetId = ref(this.$route.hash.replace('#', ''));
    const eal = document.getElementById(targetId.value);
    if (eal != null) {
      eal.scrollIntoView();
    }
  }
},

This works when I load the page from scratch, but it doesn't when I use the aforementioned router.push method. There is no error though, so the component is able to find the element linked to the requested anchor tag.

Another thing is that when I perform a hardcoded router.push from a button click, it works!


Solution

  • In vuejs there is a parent-child dependency between elements. It is not clear from the question if views are used, but I assume so, because that's a best practice.

    • router.push is for history manipulation: so if you do that from the parent main-view it will work, and inform automatically a child-view to re-render (because of the change itself)

    • but if you do the calculation deeper in the page, in a child, and want to update the entire page, you have to use the emit component event (the child informs the parents about the change of values and for a re-render)

    See this example for a demo: https://learnvue.co/tutorials/vue-emit-guide

    To put it together: having a update function in the mainView, which wants to be called from a RouterLink in a childView. This is started by updateParent - so there a emit event prev-next-click is defined.

    <script setup>
    import { RouterLink } from 'vue-router'
    </script>
    
    <script>
    export default {
      emits: ['prev-next-click'],
      methods: {
        updateParent: function(c, d) {
          // on button click update from fixture
          // emit a call from subView (child) to run the update in main App (parent) 
          this.$emit("prev-next-click", c, d);
        },
        to: function (c=this.category, d=this.today) {
          return { name: 'quote', params: { category: c, day: d }}
        },
      },
      created() {
        this.updateParent(this.$route.params.category, this.$route.params.day);
      }
    };
    </script>
    
    <template>
        <RouterLink class="button prev" :to="to(category,prev)" @click="updateParent(category,prev)">click</RouterLink>
    </template>
    

    And the mainView glue it together with RouterView (not the name of the view!)

    <script setup>
    import { RouterLink, RouterView } from 'vue-router'
    </script>
    
    <script>
    export default {
      methods: {
        update: function (c, d) {
          console.log("update c", c, "d", d);
        },
      },
    }
    </script>
    
    <template>
        <RouterView @prev-next-click="update"/>
    </template>
    

    Update to the comment:

    A problem I had to solve with the code above, was that the childView wasn't rendered properly when initialized. That's why childView always call updateParent once directly during creation by the created hook.

    See vuejs lifecycle for all other hooks - maybe the updated fits in your case.