Scoped/Nested Memory Routers #9601
Replies: 12 comments 7 replies
-
I wonder if this can be done more easily now that we have the data router concepts in 6.4. In particular, if this could be done without introducing a new API entrypoint and having to export/import a bunch of component and function instances. For instance, you could have something like this: const mainRouter = createBrowserRouter(mainRoutes)
const modalRouter = createMemoryRouter(modalRoutes)
render(
<RouterProvider router={mainRouter}>
<RouterProvider router={modalRouter} />
</RouterProvider>
) That wouldn't all be in one file, but spread out throughout the component tree. I'm just using this example to display the general structure and sources of things. In one of your modal's components that's located at the root route for function Something() {
const router = useRouterContext() // This is modalRouter from above
const navigate = router.parent.navigate // router.parent is mainRouter
return (
<div>
<Link to="..">Go back</Link> {/* This would go up above the modal to the leaf route in mainRouter */}
<button onClick={() => navigate('/profile')}>Go to your profile</button>
</div>
)
} The only new API here is useRouterContext, just to get the scope-local |
Beta Was this translation helpful? Give feedback.
-
Hello, |
Beta Was this translation helpful? Give feedback.
-
Original PR for this idea: #9112 |
Beta Was this translation helpful? Give feedback.
-
Hi there! Are there any updates on this feature? |
Beta Was this translation helpful? Give feedback.
-
Would love to see support for a nested memory router. I have a 'sidebar' app that is independent of the 'main' app and allows the user to do things such as customize the app theme, fonts etc. It's like a mini-app within the main app, and needs to have it's own set of routes. Right now I'm using this workaround which seems to be working fine. |
Beta Was this translation helpful? Give feedback.
-
FWIW, here's a detailed walk through the "modal router" pattern in v5: https://charlypoly.com/publications/react-memory-router-pattern. |
Beta Was this translation helpful? Give feedback.
-
I had this issue and noticed that <UNSAFE_RouteContext.Provider
value={{
outlet: null,
matches: [],
isDataRoute: false,
}}
>
<UNSAFE_NavigationContext.Provider value={null}>
<UNSAFE_LocationContext.Provider value={null}>
{children}
</UNSAFE_LocationContext.Provider>
</UNSAFE_NavigationContext.Provider>
</UNSAFE_RouteContext.Provider>; I created a new provider called Then, I used it like this: <ScopedRouterProvider>
<MemoryRouter>
<Routes>
<Route path="/modal/1" element={<Modal />} />
<Route path="/" element={<Page />} />
</Routes>
</MemoryRouter>
</ScopedRouterProvider>; In the modal, when I needed to use the root <RenderInParentRouterContext>
<Navigate to="/about" />
</RenderInParentRouterContext> I didn't find an easy way to use hooks with different parent/nested context in one component. Instead, I created a new component and wrapped it with I haven't tested this for all possible scenarios, but it works in my project. You can have a look here: |
Beta Was this translation helpful? Give feedback.
-
Is there any non-hack way to nest Routers today? I have an app where I want to use a MemoryRouter nested within a normal router to manage a sub-window (like iframe) within the application which has its own path. |
Beta Was this translation helpful? Give feedback.
-
I mean... I'm not sure what you mean by "non-hack" but we're taking this approach (for the exact same use case you described) and it's working perfectly so far: import React, { useLayoutEffect, useRef } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import store from '../store'
/**
* This is just a temporary solution to allow the nested `MemoryRouter` until
* `react-router` supports it out of the box.
*
* @see https://github.com/remix-run/react-router/pull/9112#issuecomment-1241077187
*/
const MemoryRouterContextWrapper = ({
children,
}: {
children: React.ReactNode
}) => {
const ref = useRef<React.ElementRef<'div'>>(null)
useLayoutEffect(() => {
const root = ref.current && createRoot(ref.current)
root?.render(
<Provider store={store}>
<React.Suspense fallback="...">{children}</React.Suspense>
</Provider>
)
return () => {
setTimeout(() => {
root?.unmount()
})
}
}, [])
return (
<div
ref={ref}
style={{
width: '100%',
height: '100%',
overflow: 'hidden',
}}
/>
)
}
export default MemoryRouterContextWrapper Obviously <MemoryRouterContextWrapper>
<MemoryRouter>
<span>xyz...</span>
<Routes>
<Route ... />
<Route ... />
<Route ... />
</Routes>
</MemoryRouter>
</MemoryRouterContextWrapper> Just to be clear though, basically any approach will be a "hack" until it's supported out of the box (which is the main discussion on this thread). The downside of what I propose above is that you have to keep |
Beta Was this translation helpful? Give feedback.
-
👋 Love this, but I'd go one step further and add support for nested hash routers as well. There's probably a bunch of use-cases for having an embedded Hash Router on a remix page that would allow for highly dynamic yet content-addressable and linkable experiences. For posterity, this is what I mentioned in Discord about it:
|
Beta Was this translation helpful? Give feedback.
-
Interested in this as well #9601 is dead ? |
Beta Was this translation helpful? Give feedback.
-
Any update on if this is considered for v7? I saw it mentioned in the Discord server (https://discord.com/channels/770287896669978684/940264701785423992/1247623644834824495) but there hasn't been much discussion around it |
Beta Was this translation helpful? Give feedback.
-
I recently saw this: https://remix.run/blog/open-development
and was hoping a proposal will help get traction on the proposed changes in this PR: #9112
I originally made this PR in July, but have not had much traction from the maintainers of react-router.
The following section is a majority of copy paste from the PR description (please take a look to see all the discussion).
Hello everyone!
My name is Austin and I am a dev on Xbox. I primarily work on Xbox Cloud Gaming. We currently leverage the
react-router
package and enjoy it.The Problem
We are looking to upgrade to v6 soon and at the same time we are doing some work around Modals and wanted to leverage a
MemoryRouter
to control a Modal stack. We thought it would be great to navigate our Modals similar to how we navigate the rest of the site.Key features include
BrowserRouter
from within a modalOur desired features led us back to
react-router
. We did not want to re-invent the wheel and thought something like aMemoryRouter
would be perfect because we wanted our Modal routing system to not be exposed to the Browser. You cannot deep link into a modal, so MemoryRouter made the most sense. It is just meant to be used for in-memory navigation, exactly the purpose of<MemoryRouter>
.So with that in mind, we went looking around to answer the question "Can you have a MemoryRouter inside of a BrowserRouter?". We ended up finding issues like the following:
#9109
#8817
#7375
The Solution
We realized it is not possible to nest Routers in v6 and it was a bummer. After looking through the
react-router
code, we came to the conclusion that is is definitely possible to enable having nested MemoryRouter's inside of other Routers. So here we are, with a PR (#9112) that enables nesting MemoryRouter's.Example Usage:
You can see this example under the new
examples/scoped-memory-router
demo.How it works
There is a new method exported called
createScopedMemoryRouterEnvironment
which returns the various components / hooks needed for a scoped memory router.This function does not return things like
BrowserRouter
since it does not make sense to have nested BrowserRouter's.It works by making almost all components / hooks into create methods. Instead of having
useLocation
use the default context, we replaced it with acreateLocationHook
that is passed a context. We then use the default context for the defaultuseLocation
export, but it then allows us to create a newuseLocation
hook inside ofcreateScopedMemoryRouterEnvironment
.This creator pattern was taken from
react-redux
as seen in their useSelector implementation.We've leveraged
react-redux
createSelectorHook
before and had a great time working with that pattern. We believe this pattern can also be used inreact-router
and have proved it out in this PR. These proposed changes would resolve issues like:#9109
#8817
#7375
FAQ
Why do you need to create unique contexts?
The implementation in this PR allows navigation of different routers from any level of the application by allowing MemoryRouters to have distinct contexts.
The createScopedMemoryRouterEnvironment call returns scoped hooks that allow you to perform actions within the scoped MemoryRouter. You can see this at play in the example here:
If you'd like to navigate the root BrowserRouter you'd use the regularly imported hooks from the lib and if you're navigating within the scoped memory router I'd anticipate you'd run this in a module in user-land code and re-export out these generated values, i.e.:
src/embedded-application/scoped-react-router.ts
src/embedded-application/component/Foo.tsx
This allows us to navigate within the scoped Memory Router, but also navigate at the Browser Router level if needed.
Cheers!
Beta Was this translation helpful? Give feedback.
All reactions