Search code examples
javascriptvuejs3vue-router4

How to inherit props from parent route with vue-router


I have the following router configuration and would like that the id of the main.parent route is converted to an integer and then passed into the child components (given props: true) for all child components.

{
  path: '/',
  component: GrandparentComponent,
  name: 'main',
  children: [{
    path: ':id',
    component: ParentComponent,
    name: 'main.parent',
    props: route => {
      return {
        // parse URL id to int
        id: parseInt(route.params.id, 10)
      };
    },

    children: [{
      path: 'details',
      name: 'main.parent.child',
      component: ChildComponent,
      props: true,
      children: [{
        ...
      }]
    }]
  }]
}

However, it seems as if the route function is only really called once (when evaluating /:id) and not when /:id/details is evaluated. The relevant code in vue-router seems to confirm this.

            const route = routeToDisplay.value;
            const matchedRoute = matchedRouteRef.value;
            const ViewComponent = matchedRoute && matchedRoute.components[props.name];
            // we need the value at the time we render because when we unmount, we
            // navigated to a different location so the value is different
            const currentName = props.name;
            if (!ViewComponent) {
                return normalizeSlot(slots.default, { Component: ViewComponent, route });
            }
            // props from route configuration
            const routePropsOption = matchedRoute.props[props.name];
            const routeProps = routePropsOption
                ? routePropsOption === true
                    ? route.params
                    : typeof routePropsOption === 'function'
                        ? routePropsOption(route)
                        : routePropsOption
                : null;
            ...
            const component = h(ViewComponent, assign({}, routeProps, attrs, {
                onVnodeUnmounted,
                ref: viewRef,
            }));
            ...

I wonder if anyone tried to solve the same problem and what they came up with a solution. Ideally, I'd like to avoid duplicating the props function for every child route.


Solution

  • There's no such feature in Vue Router for passing props to child routes that way.

    Instead, you could use provide/inject to effectively do this. The parent would provide the id property, and any descendant could inject it:

    1. In ParentComponent.vue, use toRefs() on props to get a reactive ref to the id prop.
    2. Use provide() to provide that id to any children (including child routes).
    3. Apply a key on <router-view> to ensure the view is re-rendered based on the unique id.
    4. In ChildComponent.vue, use the inject prop to inject the id from step 2.
    // ParentComponent.vue
    <script>
    /* Composition API */
    import { provide, toRefs } from 'vue'
    
    export default {
      props: ['id'],
      setup(props) {
        const { id } = toRefs(props)
        provide('id', id)
      },
    }
    
    /* Options API */
    import { toRefs } from 'vue'
    
    export default {
      props: ['id'],
      provide() {
        const { id } = toRefs(this.$props)
        return { id }
      },
    }
    </script>
    
    <template>
      <router-view :key="id" />
    </template>
    
    // ChildComponent.vue
    <script>
    export default {
      inject: ['id'],
    }
    </script>
    
    <template>
      <h2>Detail {{ id }}</h2>
    </template>
    

    Composition API demo

    Options API demo