Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]: Named Outlets #8446

Closed
itaditya opened this issue Dec 5, 2021 · 9 comments
Closed

[Feature]: Named Outlets #8446

itaditya opened this issue Dec 5, 2021 · 9 comments
Labels

Comments

@itaditya
Copy link

itaditya commented Dec 5, 2021

What is the new or updated feature that you are suggesting?

Currently we can only render one component for a given url using

<Route path="dashboard" element={<Dashboard />} />

then we can decide where this content gets rendered in the subtree using Outlet.

There should be a guide / recipe / official component which shows how different subtrees can render a specific component on the same route maybe by matching via name string.

I've made a POC on StackBlitz. It works by injecting a name to the element returned by useOutlet. And then using the name in NamedRoutes component to render specific component.

This is the API-

<Routes>
  <Route path="/" element={<Layout />}>
    <Route
      index
      element={
        <NamedRoute
          outlets={[
            {
              name: 'nav',
              content: <h4>Home Nav Content</h4>,
            },
            {
              name: 'main',
              content: <Home />,
            },
          ]}
        />
      }
    />
    <Route
      path="about"
      element={
        <NamedRoute
          outlets={[
            {
              name: 'nav',
              content: <h4>About Nav Content</h4>,
            },
            {
              name: 'main',
              content: <About />,
            },
          ]}
        />
      }
    />
  </Route>
</Routes>
function Layout() {
  return (
    <div>
      <nav>
        <Link to="/">Home</Link>
        <NamedOutlet name="nav" />
      </nav>
      <main>
        <NamedOutlet name="main" />
      </main>
    </div>
  );
}

Why should this feature be included?

  1. In previous versions of React Router, we could easily render different components on same route by just rendering <Route /> deep in the tree. Since Route now needs to be at top level, that approach is not possible so an alternative is needed.
  2. It's a common need to render different components in various places of the application for the same route.
@Girish21
Copy link

Girish21 commented Dec 5, 2021

In previous versions of React Router, we could easily render different components on same route by just rendering deep in the tree. Since Route now needs to be at top level, that approach is not possible so an alternative is needed.

looks like there is a way to do it https://reactrouter.com/docs/en/v6/faq#how-do-i-nest-routes-deep-in-the-tree

@itaditya
Copy link
Author

itaditya commented Dec 5, 2021

having a separate Routes tree can accomplish this I guess. But it comes with a lot of baggage too. Let's say a component using a react-router hook rendered in the different Routes instance trees would get different output. I would prefer having one Routes instance for the entire app as much as possible.

@Girish21
Copy link

Girish21 commented Dec 5, 2021

having a separate Routes tree can accomplish this I guess. But it comes with a lot of baggage too. Let's say a component using a react-router hook rendered in the different Routes instance trees would get different output. I would prefer having one Routes instance for the entire app as much as possible.

Looking at the source, looks like BrowserRouter/Router initialise the ReactRouter context, and Routes just generates the paths for the child Route. So having multiple Routes should not be an issue and should not give ambiguous results when accessing the states using RR hooks.

If it does, then I guess it's a bug? 😅

@itaditya
Copy link
Author

itaditya commented Dec 5, 2021

Oh ok. I might be wrong then. Will try it out.

@timdorr
Copy link
Member

timdorr commented Dec 6, 2021

As pointed out, you can just continue to nest your Route's deeper or use multiple levels of Routes. Either is fine and should work the same at the component level.

@timdorr timdorr closed this as completed Dec 6, 2021
@mauro-ni
Copy link

mauro-ni commented Sep 2, 2022

Hi everybody,
I think that named outlets could be a useful feature.
I'm refactoring my app, trying to put routing related things in one place (using outlets) and I'm struggling with a component currently using 2 <Routes> elements (same URL).

Any idea?

For now I'll leave the less important <Routes> element in place.

Thanks in advance.
Mauro

@remix-run remix-run deleted a comment from tonny008 Jul 3, 2023
@lukejagodzinski
Copy link

lukejagodzinski commented Jul 27, 2023

Is there any plan to support that? I would like to place some extra content in the menu of the parent route but still to be controlled by the child route. I did something like that using React portals but it's not working well with SSR.

I guess the problem could be using it with Remix and actually how to decide what to render and where. With pure React Router it should be easy as you have full control. However, with Remix and file convention it becomes tricky. So I thought that you could actually do something like that:

export default function AdminUsersPage() {
  const { users } = useLoaderData<typeof loader>();

  return (
    <>
      <RenderToOutlet>
        <Users users={users} />
      </RenderToOutlet>
      <RenderToOutlet name="menu">
        <UsersFilter users={users} />
      </RenderToOutlet>
    </>
  );
}

Not providing the name prop will result in rendering content into <Outlet />. Providing name will result in rendering content into named outlet ie. <Outlet name="menu" />.

In the meantime, I will handle it differently but would be great to have support for named outlets. Hope, that this feature was not abandoned or some other solution is in the works.

@lukejagodzinski
Copy link

lukejagodzinski commented Jul 27, 2023

Actually I achieved the same effect with useOutletContext. This is great feature. Just for the posterity I will paste here an example code:

// Parent
type FilterContext = {
  setFilter: (filter: React.ReactNode | null) => void;
};

export default function ParentPage() {
  const [filter, setFilter] = React.useState<React.ReactNode | null>(null);

  return (
    <div className="flex flex-row w-full h-full">
      <div className="flex-none">
        <Menu/>
        {filter}
      </div>
      <div className="flex-1">
        <Outlet context={{ setFilter } satisfies FilterContext} />
      </div>
    </div>
  );
}

export function useFilter() {
  return useOutletContext<FilterContext>();
}
// Child
export default function ChildPage() {
  const { setFilter } = useFilter();

  React.useEffect(() => {
    setFilter(<ChildPageFilter />);
    return () => setFilter(null);
  }, [setFilter]);

  return (
    <div>Content</div>
  );
}

And it works great with SSR, no hacks with portals required. I love Remix even more now :D

@ZipBrandon
Copy link

Great work! It is unfortunate that this hinges in a useEffect which does not run on SSR. I've been looking for a way to slot different components into areas and I am met with jank in my use-case because of useEffect.

brophdawg11 added a commit that referenced this issue Mar 27, 2024
…ilable when building stack trace (#8446)

Co-authored-by: Matt Brophy <matt@brophy.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants