atlassian-labs / react-resource-router

Configuration driven routing solution for React SPAs that manages route matching, data fetching and progressive rendering
https://atlassian-labs.github.io/react-resource-router
Apache License 2.0
202 stars 28 forks source link

Advice on how to do "nested" routing #93

Closed advdv closed 3 years ago

advdv commented 3 years ago

The lack of development on React router and my need for prefetching queries for React Relay has driven me to this project and I really like what I'm seeing. But I do need some advice (or an example) on how to deal with "nested" routes. I did read the existing issue on this topic but I don't fully understand what is said there: https://github.com/atlassian-labs/react-resource-router/issues/89

Let's say I have a /about with several tabs: "teams" and "history". Which would the routes /about/teams and about/history respectively. As I understand from the issue above I would need to create "slots" in my About component that can then be filled depending on the route, so something like this:

export const routes = [
  {
      path: '/about/teams',
      name: 'ABOUT_TEAMS',
      component: () => <About slot={<AboutTeams/>}/>
  },
  {
      path: '/about/history',
      name: 'ABOUT_HISTORY',
      component: () => <About slot={<AboutHistory/>}/>
  }
]

the problem with this is since component is specified as ()=> it is re-rendered on every route change. If I instead follow the example the component is not re-rendered but now I can't specify the slot for sub-routes in any way

export const routes = [
  {
      path: '/about/teams',
      name: 'ABOUT_TEAMS',
      component: About // now how to specify the "slot" property
  },
  {
      path: '/about/history',
      name: 'ABOUT_HISTORY',
      component: About
  }
]

EDIT: having read #89 again and again I'm starting to think something like this is hinted at:

export const routes = [
  {
      path: '/about/teams',
      name: 'ABOUT_TEAMS',
      component: About // now how to specify the "slot" property
      slot: <Teams/> // or maybe just 'Teams'
  },
  {
      path: '/about/history',
      name: 'ABOUT_HISTORY',
      component: About
      slot: <History/> // or just 'History'
  }
]

but then how to get to that "slot" property in the About component? useRouter returns access to the matched route but the typescript definition doesn't account for the 'slot' property?

theKashey commented 3 years ago

There is no real way to typescript(can it be a verb?) your route definition and useRouter together, you have to add some types here and here, probably with some any around by yourself.

MonicaOlejniczak commented 3 years ago

You can do it a couple ways, the first is selecting the component from something like the current route name:

const slots = {
  ABOUT_TEAMS: Teams,
  ABOUT_HISTORY: History, 
};

// Creating the slot here will isolate re-renders from the hook to this component
// Alternatively, you can use `useRouteName`:
// https://atlassian-labs.github.io/react-resource-router/#/api/hooks?id=createrouterselector
const CurrentSlot = () => {
  const [{ name }] = useRouter();
  const Slot = slots[name] || Teams;

  return <Slot />;
};

export const About = () => (
  <div>
    <h1>About</h1>
    <CurrentSlot />
  </div>
);

The second approach is to store the slot on the route, like you have provided in your edit:

type AboutRoute = Route & {
  slot: ComponentType;
};

const CurrentSlot = () => {
  const [{ route }] = useRouter();
  const Slot = (route as AboutRoute).slot || Teams;

  return <Slot />;
};

export const About = () => (
  <div>
    <h1>About</h1>
    <CurrentSlot />
  </div>
);

export const routes = [
  {
      path: '/about/teams',
      name: 'ABOUT_TEAMS',
      component: About,
      slot: Teams, // or <Teams />
  },
  {
      path: '/about/history',
      name: 'ABOUT_HISTORY',
      component: About,
      slot: History, // or <History />
  }
];

We should probably have better support for the custom route properties by either making the type definition less strict or supporting generics, since this is a common use-case.

advdv commented 3 years ago

@MonicaOlejniczak Thank you for the in-depth examples, this is exactly what I was looking for! Keep up the great work with this project and it would indeed be nice to expand the Typescript types to account for this use case.

NOTE: My Typescript skills are unfortunately not at the level that I can contribute it myself at this point but maybe if I spend some more time with the Router I'll come around and see if I can add it.