sjbuysse
sjbuysse

Reputation: 4124

Render modal on a route with the parent route rendered in background

I'm using ReachUI Dialog to render a Dialog (Modal). The Dialog is routable at the route products/create/, as you can see in the code below.

Is it possible to render both the ProductsTable (which is located at the parent route products/) and the Dialog, when I route to products/create so that I can see the ProductsTable component in the background of the modal? I'm hoping to achieve this with react-router.

This is what the code looks like now:

...
import { Switch, Route, useRouteMatch, match, useHistory, } from "react-router-dom";
import { Dialog } from "@reach/dialog";

export function ProductsPage() {
  const { url }: match = useRouteMatch();
  const { push } = useHistory();

  const gotToCreateProduct = () => push(`${url}/create`);

  return (
      <Switch>
        <Route exact path={url}>
          <Page title="Products" onCreateButtonClick={gotToCreateProduct}>
            <ProductsTable></ProductsTable>
          </Page>
        </Route>
        <Route path={`${url}/create`}>
            <Dialog isOpen={true} onDismiss={() => push(url)}>
                <ProductForm></ProductForm>
            </Dialog>
        </Route>
      </Switch>
  );
}

Upvotes: 8

Views: 13784

Answers (5)

Akshay K Nair
Akshay K Nair

Reputation: 1476

React router v6

You'll have to use an Outlet in your nested component;
<BrowserRouter>
   <Routes>
      <Route path="/home" element={<Home />}>
         <Route path="modal" element={<Modal />} />
      </Route>
   </Routes>
</BrowserRouter>

And inside the Home component add an Outlet;

import { Outlet } from "react-router-dom";
export default function Home(){
   return(
      <>
         Home Code is here
         <Outlet />
      </>
   )
}

so now, whenever that route /home/modal is opened, it'll open the modal by default. Make sure to keep the prop isOpen true by default as the rendering will be controlled by the react router now.

Upvotes: 10

Ole August St&#248;le
Ole August St&#248;le

Reputation: 779

@Akshay K Nair's answer is almost perfect, just wrong position of <Outlet />. The correct use of <Outlet /> is to have the parent router component render it (read more here). Check out this React Router Dom v6 example:

Router

export const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />,
    children: [
      {
        path: "/:id",
        element: (
          <ModalRoute>
            <Detail />
          </ModalRoute>
        ),
      },
    ],
  },
])

Parent Component

const Home = () => {
  return (
    <>
      Insert parent code
      <Outlet />
    </>
  )
}

Modal Route

const ModalRoute = ({ children }) => {
  const [opened, setOpened] = useState(false)
  const location = useLocation()
  const navigate = useNavigate()

  useEffect(() => {
    const id = location.pathname.split("/")[1]
    if (id) {
      setOpened(true)
    }
  }, [location.pathname])

  const handleClose = () => {
    navigate("/")
    setOpened(false)
  }

  return (
    <Modal opened={opened} onClose={handleClose}>
        {children}
    </Modal>
  )
}

Upvotes: 9

sjbuysse
sjbuysse

Reputation: 4124

Figured it out myself, basically you just have to put 2 Switch components next to each other, and remove the exact attribute of the parent Route. This way, both Switches will have a match and render their routes. Makes sense & it works.

    return (
        <>
            <Switch>
                <Route path={url}>
                    <Page title="Products" onCreateButtonClick={gotToCreateProduct}>
                        <ProductsTable></ProductsTable>
                    </Page>
                </Route>
            </Switch>
            <Switch>
                <Route path={`${url}/create`}>
                    <Dialog show={true} title={'Create product'}
                            onClose={() => push(url)}><CreateProduct></CreateProduct></Dialog>
                </Route>
                <Route path={`${url}/:productId/edit`}>
                    <Dialog show={true} title={'Edit product'} onClose={() => push(url)}><EditProduct></EditProduct></Dialog>
                </Route>
            </Switch>
        </>
    );

Upvotes: 8

Leo Jweda
Leo Jweda

Reputation: 2487

This should be a start: https://reactrouter.com/web/example/modal-gallery

They conditionally render it as a modal only when you navigate to it from the parent. You can modify it so it always renders as a modal.

Upvotes: -2

Damien
Damien

Reputation: 633

by changing your first route to

<Route path={`${url}*`}>
  <Page title="Products" onCreateButtonClick={gotToCreateProduct}>
    <ProductsTable></ProductsTable>
  </Page>
</Route>

your component will be displayed for every route that starts with url and by removing the Switch component you will be able to match multiple routes

Upvotes: 0

Related Questions