fix(vue): update props when navigating to new parameterized route (#23189)

This commit is contained in:
Liam DeBeasi
2021-04-15 12:37:50 -04:00
committed by GitHub
parent 15abc181aa
commit 35c8802c22
6 changed files with 80 additions and 28 deletions

View File

@ -55,16 +55,31 @@ export const IonRouterOutlet = defineComponent({
// The base url for this router outlet // The base url for this router outlet
let parentOutletPath: string; let parentOutletPath: string;
watch(matchedRouteRef, (currentValue, previousValue) => {
/** /**
* We need to make sure that we are not re-rendering * We need to watch the route object
* the same view if navigation changes in a sub-outlet. * to listen for navigation changes.
* This is mainly for tabs when outlet 1 renders ion-tabs * Previously we had watched matchedRouteRef,
* and outlet 2 renders the individual tab view. We don't * but if you had a /page/:id route, going from
* want outlet 1 creating a new ion-tabs instance every time * page/1 to page/2 would not cause this callback
* we switch tabs. * to fire since the matchedRouteRef was the same.
*/ */
if (currentValue !== previousValue) { watch([route, matchedRouteRef], ([currentRoute, currentMatchedRouteRef], [_, previousMatchedRouteRef]) => {
/**
* If the matched route ref has changed,
* then we need to set up a new view item.
* If the matched route ref has not changed,
* it is possible that this is a parameterized URL
* change such as /page/1 to /page/2. In that case,
* we can assume that the `route` object has changed,
* but we should only set up a new view item in this outlet
* if that last matched view item matches our current matched
* view item otherwise if we had this in a nested outlet the
* parent outlet would re-render as well as the child page.
*/
if (
currentMatchedRouteRef !== previousMatchedRouteRef ||
currentRoute.matched[currentRoute.matched.length - 1] === currentMatchedRouteRef
) {
setupViewItem(matchedRouteRef); setupViewItem(matchedRouteRef);
} }
}); });

View File

@ -46,7 +46,8 @@ const routes: Array<RouteRecordRaw> = [
}, },
{ {
path: '/routing/:id', path: '/routing/:id',
component: () => import('@/views/RoutingParameter.vue') component: () => import('@/views/RoutingParameter.vue'),
props: true
}, },
{ {
path: '/routing/:id/view', path: '/routing/:id/view',
@ -71,7 +72,8 @@ const routes: Array<RouteRecordRaw> = [
}, },
{ {
path: ':id', path: ':id',
component: () => import('@/views/Folder.vue') component: () => import('@/views/Folder.vue'),
props: true
} }
] ]
}, },

View File

@ -5,19 +5,19 @@
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-menu-button></ion-menu-button> <ion-menu-button></ion-menu-button>
</ion-buttons> </ion-buttons>
<ion-title>{{ folder }}</ion-title> <ion-title>{{ $props.id }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content :fullscreen="true"> <ion-content :fullscreen="true">
<ion-header collapse="condense"> <ion-header collapse="condense">
<ion-toolbar> <ion-toolbar>
<ion-title size="large">{{ folder }}</ion-title> <ion-title size="large">{{ $props.id }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<div id="container"> <div id="container">
<strong class="capitalize">{{ folder }}</strong> <strong class="capitalize">{{ $props.id }}</strong>
<p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p> <p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
</div> </div>
</ion-content> </ion-content>
@ -26,8 +26,6 @@
<script lang="ts"> <script lang="ts">
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar } from '@ionic/vue'; import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
import { useRoute } from 'vue-router';
import { ref, computed, watch } from 'vue';
export default { export default {
name: 'Folder', name: 'Folder',
@ -40,16 +38,8 @@ export default {
IonTitle, IonTitle,
IonToolbar, IonToolbar,
}, },
setup() { props: {
const route = useRoute(); id: { type: String, default: 'Inbox' }
const folder = ref(route.params.id || 'Inbox');
const matchedFolder = computed(() => route.params.id);
watch(matchedFolder, () => {
folder.value = matchedFolder.value as string;
})
return { folder }
} }
} }
</script> </script>

View File

@ -18,8 +18,11 @@
<ion-button id="parameter-view" :router-link="'/routing/' + $route.params.id + '/view'">Go to Single View</ion-button> <ion-button id="parameter-view" :router-link="'/routing/' + $route.params.id + '/view'">Go to Single View</ion-button>
<ion-button router-link="/routing/abc">Go to Parameter Page ABC</ion-button>
<ion-button router-link="/routing/xyz">Go to Parameter Page XYZ</ion-button>
<div class="ion-padding" id="parameter-value"> <div class="ion-padding" id="parameter-value">
{{ $route.params.id }} {{ $props.id }}
</div> </div>
</ion-content> </ion-content>
</ion-page> </ion-page>
@ -39,6 +42,9 @@ import {
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
props: {
id: { type: String, default: 'my default' }
},
components: { components: {
IonButton, IonButton,
IonBackButton, IonBackButton,

View File

@ -275,11 +275,15 @@ describe('Tabs - Swipe to Go Back', () => {
cy.ionPageVisible('tab1') cy.ionPageVisible('tab1')
}); });
it('should swipe and abort', () => { // TODO: Flaky if test runner is slow
// Delays between gesture movements
// cause swipe back gesture to think
// velocity is higher than it actually is
/*it('should swipe and abort', () => {
cy.ionSwipeToGoBack(); cy.ionSwipeToGoBack();
cy.ionPageHidden('home'); cy.ionPageHidden('home');
cy.ionPageVisible('tab1'); cy.ionPageVisible('tab1');
}); });*/
it('should swipe and go back to home', () => { it('should swipe and go back to home', () => {
cy.ionSwipeToGoBack(true); cy.ionSwipeToGoBack(true);

View File

@ -358,4 +358,39 @@ describe('Routing', () => {
const cmpAgain = wrapper.findComponent(Page1); const cmpAgain = wrapper.findComponent(Page1);
expect(cmpAgain.props()).toEqual({ title: 'abc Title' }); expect(cmpAgain.props()).toEqual({ title: 'abc Title' });
}); });
// Verifies fix for https://github.com/ionic-team/ionic-framework/pull/23189
it('should update props on a parameterized url', async () => {
const Page = {
props: {
id: { type: String, default: 'Default ID' }
},
components: { IonPage },
template: `<ion-page>{{ $props.id }}</ion-page>`
}
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes: [
{ path: '/page/:id', component: Page, props: true },
{ path: '/', redirect: '/page/1' }
]
});
router.push('/');
await router.isReady();
const wrapper = mount(App, {
global: {
plugins: [router, IonicVue]
}
});
const page = wrapper.findComponent(Page);
expect(page.props()).toEqual({ id: '1' });
router.push('/page/2');
await waitForRouter();
expect(page.props()).toEqual({ id: '2' });
});
}); });