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
Only
<Programs />
in"/programs"
renders anOutlet
This allows only the immediately nested routes to be rendered into the parent route component's Outlet
, e.g:
Navigate
on the "/programs"
index routeDiscoverPrograms
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." };
});
};