Routing requests to update part of child component using React router

Viewed 29

I'm attempting to create layout components for sections of my application which include side navigation panes for navigating between pages within that layout component.

I have react router set up using a BrowserRouter within my Main view. This main view includes the top navigation bar with a menu. When I click the Menu drop down, I can select a section of the app to browse to. This will open a layout which includes the side navigation bar. When a user clicks a link within the side nav bar, it should display content within a content pane on the right side of the screen.

For more info, please see the code example below:

https://codesandbox.io/s/compassionate-snowflake-eqzh0y

import * as React from "react";
import AppLayout from "@cloudscape-design/components/app-layout";
import BreadcrumbGroup from "@cloudscape-design/components/breadcrumb-group";
import Flashbar from "@cloudscape-design/components/flashbar";
import SideNavigation from "@cloudscape-design/components/side-navigation";
import SplitPanel from "@cloudscape-design/components/split-panel";
import TestDetails from "./TestDetails";
import TestTable from "./TestTable";
import { Route, Routes, useNavigate } from "react-router-dom";

function TestLayout() {
  const [flashMessages, setFlashMessages] = React.useState(Array);
  const [testDetails, setTestDetails] = React.useState("");

  let navigate = useNavigate();

  function handleSideNavigationItemClick(e) {
    e.preventDefault();
    console.log("Navigating to " + e.detail.href);
    navigate(e.detail.href);
  }

  const showFlashMessage = (type, header, body) => {
    let message = {
      type: type,
      dismissible: true,
      dismissLabel: "Dismiss message",
      header: header,
      onDismiss: () =>
        setFlashMessages((flashMessages) =>
          flashMessages.filter((item) => item.id !== "flash_message_1")
        ),
      content: <>{body}</>,
      id: "flash_message_1"
    };

    setFlashMessages([...flashMessages, message]);
  };

  React.useEffect(() => {
    setTestDetails("Some test details");
  }, []);

  return (
    <React.Fragment>
      <AppLayout
        breadcrumbs={
          <BreadcrumbGroup items={[{ text: "Test Main", href: "/testing" }]} />
        }
        content={
          <Routes>
            <Route
              element={<TestTable showFlashMessage={showFlashMessage} />}
              path="/testing/viewTests"
            />
            <Route
              element={
                <TestDetails
                  details={testDetails}
                  showFlashMessage={showFlashMessage}
                />
              }
              path="/testing/viewTestDetails"
            />
          </Routes>
        }
        contentType="table"
        navigation={
          <SideNavigation
            header={{ href: "#/", text: "Testing" }}
            items={[
              {
                type: "section",
                text: "Tests",
                items: [
                  {
                    type: "link",
                    text: "View Test Details",
                    href: "/testing/viewTestDetails"
                  },
                  {
                    type: "link",
                    text: "View All Tests",
                    href: "/testing/viewTests"
                  }
                ]
              }
            ]}
            onFollow={handleSideNavigationItemClick}
          />
        }
        notifications={<Flashbar items={flashMessages} />}
        splitPanel={
          <SplitPanel
            header={"Test Info"}
            i18nStrings={{
              preferencesTitle: "Preferences",
              preferencesPositionLabel: "Split panel position",
              preferencesPositionDescription:
                "Choose the default split panel position for the service.",
              preferencesPositionSide: "Side",
              preferencesPositionBottom: "Bottom",
              preferencesConfirm: "Confirm",
              preferencesCancel: "Cancel",
              closeButtonAriaLabel: "Close panel",
              openButtonAriaLabel: "Open panel",
              resizeHandleAriaLabel: "Slider"
            }}
          >
            Select a test from the table above to view more info
          </SplitPanel>
        }
        stickyNotifications={true}
        toolsHide={true}
      />
    </React.Fragment>
  );
}

export default TestLayout;

In the example above within the TestLayout component, I'm placing the routes in the content parameter in the AppLayout component to render when the page loads. When the click events in the side nav bar fire, it should navigate to the components I specify in those routes. Instead, when I click a link, the event fires, navigates to the new link, but no components are updated on the screen.

Also, the child components in the layout view share data from the parent layout component like functions for flash messages, state data, etc.

Is this the correct location for the routes to be placed? Should all of the routes only be stored in the index.js file in this example, and if so, how should data be passed to the child components?

1 Answers

The root Routes component is already rendering the "/testing" part of the path.

<Routes>
  <Route index element={<> </>} />
  <Route path="/testing/*" element={<TestLayout />} />
</Routes>

The routes that TestLayout is rendering will be built relative to the parent Routes component's path. You should omit the leading "/testing" from the descendent routes so their path is appended to "/testing" to result in "/testing/viewTests" and "/testing/viewTestDetails".

content={
  <Routes>
    <Route
      element={<TestTable showFlashMessage={showFlashMessage} />}
      path="/viewTests"
    />
    <Route
      element={
        <TestDetails
          details={testDetails}
          showFlashMessage={showFlashMessage}
        />
      }
      path="/viewTestDetails"
    />
  </Routes>
}

Edit routing-requests-to-update-part-of-child-component-using-react-router

Related