1

I am creating a project with react-router-dom where I want the URL to hold an id. The id will be used to fetch information about the item with that id from a database and render it on the page. I've already successfully implemented data loading based on the id in the url.

My issue is that react-router-dom seems to interpret the url with the id as an unknown route and redirects it to my error page.

This is how my routes are set up:

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <Error />,
    children: [
      { path: "/", element: <Home /> },
      { path: "progress", element: <Progress /> },
      { path: "logworkout", element: <LogWorkout /> },
      {
        path: "programs",
        element: <Programs />,
        children: [
          { index: true, element: <Navigate to="myprograms" replace /> },
          { path: "discover", element: <DiscoverPrograms /> },
          {
            path: "myprograms",
            element: <MyPrograms />,
            children: [
              {
                path: ":programID",
                element: <ProgramView />,
                loader: programViewLoader,
              },
            ],
          },
        ],
      },
      { path: "product", element: <Product /> },
      { path: "contact", element: <Contact /> },
    ],
  },
]);

I basically want /programs/myprograms/:programID to render instead of the error page. I think I followed the tutorial on react-router-dom's documentation pretty well and I can't figure out what I'm missing.

Edit: Only <Programs/> in /programs renders an Outlet

import { Outlet } from "react-router-dom";

const Programs = () => {
  return (
    <div className="grid overflow-hidden" style={{gridTemplateColumns: "auto 1fr"}}>
      <SideBar />
      <div className="overflow-auto">
        <Outlet />
      </div>
    </div>
  );
};

export default Programs;

Edit: The issue isn't about the dynamic route but about the loader function I'm using for <ProgramView/>. Apparently, I'm not returning a value in the loader.

import Cookies from "js-cookie";
import { refreshToken } from "../../../../util/auth";

export const programViewLoader = ({ params }) => {
  refreshToken()
    .then((refresh) => {
      return fetch("http://localhost:5000/program/load-program", {
        method: "POST",
        headers: {
          "Content-type": "application/json",
          Authorization: `Bearer ${Cookies.get("accessToken")}`,
        },
        body: JSON.stringify({
          programID: params.programID,
        }),
      });
    })
    .then((response) => {
      if (!response.ok) {
        return response.json().then((data) => {
          throw { error: data.error, status: response.status };
        });
      }
      return response.json();
    })
    .then((data) => {
      console.log(data.program);
      return data.program;
    })
    .catch((error) => {
      return { error: "An error occurred while loading the program." };
    });
};

However the console.log() always prints something so I'm not sure how it isn't returning anything

1
  • Do all the routed components in that path chain (e.g. Programs and MyPrograms) render an Outlet component for the nested routes to render their element content into? Can you edit to include these components for a more complete minimal reproducible example? Commented Aug 10, 2023 at 7:00

1 Answer 1

1

Only <Programs /> in "/programs" renders an Outlet

This allows only the immediately nested routes to be rendered into the parent route component's Outlet, e.g:

  • Navigate on the "/programs" index route
  • DiscoverPrograms on "/programs/discover"
  • MyPrograms on "/programs/myprograms"

Any of these components would need to, yet again, render another Outlet of their own for any nested routes they may be rendering.

const MyPrograms = () => {
  ...

  return (
    ...

    <Outlet />

    ...
  );
};

Now ProgramView should be renderable via MyPrograms' Outlet via Programs' Outlet on "/programs/myprograms/:programID".

The programViewLoader isn't returning a value. The refreshToken function returns a Promise chain, but then you need to actually return the result of the Promise chain from the loader function.

export const programViewLoader = ({ params }) => {
  return refreshToken() // <-- return Promise chain!
    .then((refresh) => {
      return fetch("http://localhost:5000/program/load-program", {
        method: "POST",
        headers: {
          "Content-type": "application/json",
          Authorization: `Bearer ${Cookies.get("accessToken")}`,
        },
        body: JSON.stringify({
          programID: params.programID,
        }),
      });
    })
    .then((response) => {
      if (!response.ok) {
        return response.json().then((data) => {
          throw { error: data.error, status: response.status };
        });
      }
      return response.json();
    })
    .then((data) => {
      console.log(data.program);
      return data.program;
    })
    .catch((error) => {
      return { error: "An error occurred while loading the program." };
    });
};
Sign up to request clarification or add additional context in comments.

3 Comments

I added the Outlet but it still redirected me to an error page. After getting rid of my custom error page, react-router-dom's error page said my loader function wasn't returning a value which is a completely different issue. Thanks for pointing out the Outlet problem to me! Makes sense
@JiaHuang You should return the Promise chain that refreshToken starts.
@JiaHuang Welcome, glad to help. Yeah, those can sometimes be tricky to see, especially when looking at your own code. Cheers and good luck!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.