Overview
This video will walk through building a Remix app that shows a list of teams and then a detailed view of each team. On the detailed page, we will have a list of players, which when clicked will show a detailed view of the player. The purpose of all of this is to show how to use Nested Routes In Remix
- The parent view/container will hold the navigation and the child components will be rendered in the provided Outlet
- We also show how to use the OutletContext that is provided for you by react-router
This is what the final directory structure with files will look like
Video
Source Code
Code and Descriptions
The first thing we want to do is have the index.tsx
redirect to our main page. I did not know of another way to do it using the router, so I just redirect in the loading of the index page
// index.tsx
import { redirect } from "remix";
export const loader = async () => {
return redirect("/teams");
};
export default function Index() {
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
<h1>Welcome to Remix</h1>
</div>
);
}
Next, we create the root of the team pages we will be working on within the application. When the browser I directed to render /teams
we will render teams.tsx
And /teams/index.tsx
// teams.tsx
import { Link, Outlet, useLocation } from "remix";
export default function Teams() {
const teams = [
{
id: 1,
name: "Team One",
players: [
{
id: 1,
name: "player one team one",
},
{
id: 2,
name: "player two team one",
},
{
id: 3,
name: "player three team one",
},
],
},
{ id: 2, name: "Team Two" },
{ id: 3, name: "Team Three" },
];
// used for displaying the current application path
const location = useLocation();
return (
<div
style={{
borderWidth: 1,
borderColor: "grey",
border: "solid",
padding: 8,
}}
>
<h1>TEAMS</h1>
<Link to="/" style={{ margin: 8 }}>
Home
</Link>
<Link to="create" style={{ margin: 8 }}>
Add New Team
</Link>
<div style={{ margin: 8, marginTop: 32, background: "grey" }}>
<!-- all of the pages in the /teams directory will be -->
<!-- rendered here at this outlet, we can also pass -->
<!-- context information through the router -->
<Outlet context={[teams]} />
</div>
<pre>{location.pathname}</pre>
</div>
);
}
this is the code for /teams/index.tsx
. here we are just rendering a list of teams which was passed down through the context that is defined in the router. We use the hook useOutletContext
to get access to the context properties.
// /teams/index.tsx
import { Link, useOutletContext } from "remix";
export default function TeamsIndex() {
const [teams] = useOutletContext() as any;
return (
<div>
<div
style={{
padding: 16,
borderWidth: 1,
borderColor: "grey",
border: "solid",
}}
>
<p>This is where the individual teams will appear</p>
{teams?.map((t: any) => (
<Link to={`/teams/${t.id}`}>
<p>{t.name}</p>
</Link>
))}
</div>
</div>
);
}
As we loop through the teams in the array we got through the context, we want to be able to drill down, but keep the layout/framework around the TeamsIndex
component. We do that by the way we structure the path for the next route.
<Link to={`/teams/${t.id}`}>
<p>{t.name}</p>
</Link>
The route /teams/<id>
will be rendered in the same outlet that was defined in the /teams/index.tsx
.
So now to see the detailed page, $teamId.tsx
, with the team information and the list of players on the team, here is what the page would look like. The $
in front of the name of the file is called parameterized route... meaning that when the route is resolved I will have access to a teamId
param in the component, that value will be set when the route is set in the referring component
// $teamId.tsx
import { Link, useOutletContext, useParams } from "remix";
export default function Team() {
// get list of teams from context
const [teams] = useOutletContext() as any;
// the parameter is derived from the name of the file
const { teamId } = useParams();
// use parameter and the context to get specific team
const team = teams[parseInt(teamId as string) - 1];
return (
<div style={{ padding: 16 }}>
<p>{team?.name}</p>
{team?.players?.map((p: any) => (
<div style={{ paddingTop: 10 }}>
<Link to={`/teams/${teamId}/player/${p.id}`}>
<div>{p.name}</div>
</Link>
</div>
))}
<div style={{ paddingTop: 16 }}>
<Link to="/teams">
<button type="button" className="button">
Back
</button>
</Link>
</div>
</div>
);
}
This pattern in the code above should start to look familiar at this point since it is very similar to how we displayed the list of teams in a nested component.
Finally our last route /teams/$teamId/player/$playerId
will show us the specific player.
import { Link, useOutletContext, useParams } from "remix";
export default function Player() {
const [teams] = useOutletContext() as any;
const { teamId, playerId } = useParams();
const team = teams[parseInt(teamId as string) - 1];
const player = team.players[parseInt(playerId as string) - 1];
return (
<div style={{ padding: 16 }}>
<p>{team?.name}</p>
<p>{player?.name}</p>
<div style={{ paddingTop: 16 }}>
<Link to={`/teams/${teamId}`}>
<button type="button" className="button">
Back
</button>
</Link>
</div>
</div>
);
}
Links
- Remix Docmentation - https://remix.run/docs/en/v1
- Source Code - https://stackblitz.com/edit/node-6sjyfk
- Remix Playlist - https://buff.ly/3GuKVPS
- Outlet Context React Router - https://reactrouterdotcom.fly.dev/docs/en/v6/api#useoutletcontext
Top comments (0)