Understanding how routes and components work in Ionic (component re created instead of reused)

Hey guys! I’m new to both Ionic and Vue, and I’m still grasping their concepts, so bear with me :slight_smile:.

So the TL;DR → seems like my components are always re created when I replace the current route with a new route. And as I said, I’m new to Vue and Ionic, but I feel like this should work the other way around: components should always be reused and not re created, although the opposite is happening. The route uses the same component (and sub components), but it is being created again each time the URL changes and I loose all of my data. Is this the correct behavior?

I’m using the following packages:

    "@ionic/vue": "^6.0.8",
    "@ionic/vue-router": "^6.0.8",
    "pinia": "^2.0.11",
    "vue": "^3.2.25",
    "vue-router": "^4.0.12"

And in router/index.js I have a the following router structure:

import { createRouter, createWebHistory } from "@ionic/vue-router";

const routes = [
  ...otherRoutes,
  {
    path: "/:userId/message",
    redirect: to => `/${to.params.userId}/message/A`
  },
  {
    path: "/:userId(\\d+)/message/:messageId",
    component: () => import("../views/MessagePage.vue"),
    name: "message",
    props: true,
  },
];

const router = createRouter({
  history: createWebHistory(BASE_PATH),
  routes,
});

export default router;

I have three types of messages (let’s call them A, B and C), so I created three components, one for each, which will be loaded inside of MessagePage.vue. I’m just trying to use the router’s messageId param to hold the information for the sub component I need to select on the first place (ComponentA, ComponentB or ComponentC).

So my MessagePage.vue looks something like (simplified):

<template>
  <ion-page>
    <ion-content class="message-page">
      <ComponentA v-if="inCompA" />
      <ComponentB v-if="inCompB"/>
      <ComponentC v-if="inCompC"/>
    </ion-content>
    <ion-footer>
      <ion-toolbar>
      <ion-segment class="segment">
        <ion-segment-button
          value="A"
          layout="icon-start"
          @click="selectMessageType"
        >
          <ion-label>A</ion-label>
        </ion-segment-button>
        <ion-segment-button
          value="B"
          layout="icon-start"
          @click="selectMessageType"
        >
          <ion-label>B</ion-label>
        </ion-segment-button>
        <ion-segment-button
          value="C"
          layout="icon-start"
          @click="selectMessageType"
        >
          <ion-label>C</ion-label>
        </ion-segment-button>
      </ion-segment>
      </ion-toolbar>
    </ion-footer>
  </ion-page>
</template>

<script>
export default {
  // ... import components
  props: ['userId', 'messageId'],

  methods: {
    selectMessageType(evt) {
      const messageType = evt.target.value;
      this.$router.replace({
        name: "message",
        params: { userId: this.userId, messageType },
      });
    },
    computed: {
      inCompA() {
        return this.messageType === "A";
      },
      inCompB() {
        return this.messageType === "B";
      },
      inCompC() {
        return this.messageType === "C";
      },
    },
  }
}
</script>

So I have some data stored in my MessagePage.vue component, gotten from the “child” components (ComponentA, ComponentB and ComponentC), which gets lost when I switch between components with selectMessageType.

Thanks in advance for any hint.

you dont need to do router replace if you are just trying to change the component from A, B, and C.

vue is reactive so when you change a reactive variable, it will re-render the vue.

<template>
  <ComponentA v-if="inCompA" />
  <ComponentB v-if="inCompB" />
  <ComponentC v-if="inCompC" />

  <button @click="selectMessageType('A')">A</button>
  <button @click="selectMessageType('B')">B</button>
  <button @click="selectMessageType('C')">C</button>
</template>

<script>
import ComponentA from './components/ComponentA.vue';
import ComponentB from './components/ComponentB.vue';
import ComponentC from './components/ComponentC.vue';
export default {
  components: {
    ComponentA,
    ComponentB,
    ComponentC,
  },
  props: ['userId', 'messageId'],
  data() {
    return { messageType: 'A' };
  },
  methods: {
    selectMessageType(_value) {
      this.messageType = _value;
      // this.$router.replace({
      //   name: 'message',
      //   params: { userId: this.userId, messageType },
      // });
    },
  },
  computed: {
    inCompA() {
      return this.messageType === 'A';
    },
    inCompB() {
      return this.messageType === 'B';
    },
    inCompC() {
      return this.messageType === 'C';
    },
  },
};
</script>
1 Like

Thanks for taking the time to reply Aaron!

I know that’s an option, but I would like to keep track of the component I’m using, so that if the user refreshes the page, it lands in the same component. I’ll probably end up going with this, but still I would like to understand how this works in Ionic, because it seems like it’s doing the opposite of what vue-router would do (if I understood correctly): always reuse the component. Or maybe I’m missing WHEN the component will not be reused and be recreated. If someone could help me understand that, I would appreciate it very much.

This is a good summary of how Ionic handles Page Lifecycles - Vue Lifecycle | Ionic Documentation.

1 Like

Hey twestrick, thanks for replying. I had read that, but it wasn’t 100% clear to me. I’m guessing the relevant part is where it says

When an app is wrapped in <ion-router-outlet> , Ionic Framework treats navigation a bit differently. When you navigate to a new page, Ionic Framework will keep the old page in the existing DOM, but hide it from your view and transition the new page. The reason we do this is two-fold:

  1. We can maintain the state of the old page (data on the screen, scroll position, etc…).
  2. We can provide a smoother transition back to the page since it is already there and does not need to be created.

I guess what confuses me is the fact that I’m navigating to the same page with different params, which uses the same component and I’m replacing it in history. So there’s no need to keep the old component around to go back or keep its state.

Maybe this could be a nice feature request: If you’re replacing a route with the same route but different params, just reuse that component. I guess that’s faster than recreating the component.
And the docs should maybe be a bit more explicit, I might submit a request for that later.

So, v-if will remove the component from the DOM which is a Vue thing, not an Ionic thing. If you want the component to stick around, you need to use v-show. v-show will add your three components to the DOM and use CSS to hide the ones that shouldn’t be shown based on the variable passed to v-show.

v-if vs. v-show reference

are you building a mobile app or a website? because if you are building a website… why are you starting with Ionic Framework? If you are building a mobile app, the user cannot refresh the page.

I think that you really need to start with what you are trying to accomplish not how can I make what I have done work. Since you stated you are new to both vue and ionic starting with some code and trying to make it contort to what you want isn’t the best approach.

IMHO. the approach that you are taking is not the correct approach, even if you can find a way to make it work

are you building a mobile app or a website?

Both :slight_smile:

because if you are building a website… why are you starting with Ionic Framework?

Let me just quote the first couple lines of https://ionicframework.com/

One codebase. Any platform. Now in Vue.

An open source mobile toolkit for building high quality, cross-platform native and web app experiences. Move faster with a single code base, running everywhere with JavaScript and the Web.

That’s why.

So, as I said before, I’m new to both Vue and Ionic and I think it’s not stressed enough in the docs (it’s there, but you need to know how to read between the lines) that Ionic Router uses the exact OPPOSITE approach that Vue Router uses in regards to reusing components. That being said, I understand now that the best approach is to just not do what I was trying to do, although it sounds like a perfectly valid use case for a Router.