React Router

Matthew MacFarquhar
4 min readJul 17, 2024

--

Introduction

I have been reading this book on React 18 and its best practices. As I go through this book, I will distill the core pieces of the most interesting tid-bits in this series of articles on React. In this article we will talk about adding “routes” to our single-page web app, which enables us to navigate to different pages via the url. We will be using react-router to achieve this.

Basic Route Setup

Our basic route setup will involve some simple routes which render components when navigated to.

Root

First, we will set up a root component, this is a common practice when we want something like a navbar to be rendered on every page in our app.

const Root = () => (
<>
<ul className="menu">
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/pokemons">Pokemons</Link></li>
</ul>
<div>
<Outlet/>
</div>
</>
);

The Root component has links to several routes we will create: Home, About and Pokemons. The <Outlet/> component is a special react-router component that acts a placeholder for the content of child routes, so all of our pages will have the common menu followed by their own content.

RouterProvider

Now we will construct our RouterProviderComponent inside our App component.

export const App: FC = () => {
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<Root/>}>
<Route index element={<Home/>}/>
<Route path="/about" element={<About/>}/>
<Route path="/pokemons">
<Route index element={<Pokemons/>} loader={dataLoader}/>
<Route path=":pokemonId" element={<Pokemons/>} loader={dataLoader}/>
</Route>
<Route path="*" element={<Error404/>}/>
</Route>
)
);

return (
<div className="App">
<RouterProvider router={router}/>
</div>
)
}

We first create a router object by calling functions react-router provides us to create a router from some elements. We have our Root route which will render out our root component. The Root route has sub-routes which will populate the <Outlet/> placeholder: index (i.e. the thing that renders if no sub-route is given) /about and /pokemons, anything else is routed to the error page.

Below are the components for the error page and the about page

export const Error404 = () => (
<div className="Error404">
<h1>Error404</h1>
</div>
)
export const About = () => (
<div className="About">
<h1>About</h1>
</div>
)

At this point, we have a basic react app where you can navigate to different pages via url paths. Next, we will discuss a more complex route which takes in url path params and loads data.

Complex Route Example

Often times, simple navigation is not enough. We want to augment our pages with some data and pull in values from the url.

Data Loader

Our data loader allows us to pull in some data that is needed by the sub-route before it loads.

export const dataLoader = async () => {
const response = await fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
const data = await response.json()
return data.results
}

For our example, we simply query the pokemon API. The data is then used in the component as follows.

const pokemons: any = useLoaderData();

URL Based Hooks

In our pokemons route, we saw we had two sub-routes. One which had no path params and one which took in a pokemonId path param. We can use that path param to change what we display in our component.

export const Pokemons = () => {
const [selectedPokemon, setSelectedPokemon] = useState<any>(null)
const pokemons: any = useLoaderData();
const naviagtion = useNavigation();
const {pokemonId = 0} = useParams();

const imageUrl = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/';

useEffect(() => {
if (Number(pokemonId) > 0 && pokemons) {
setSelectedPokemon(pokemons[Number(pokemonId) - 1]);
} else {
setSelectedPokemon(null);
}
}, [pokemons, pokemonId]);

const renderPokedex = () => (
<div className="Pokemons">
<h1>Pokemons</h1>
{pokemons.map((pokemon: any, index: number) => (
<div key={pokemon.name}>
<h2>{index + 1} {pokemon.name}</h2>
<Link to={`/pokemons/${index +1}`}>
<img
src={`${imageUrl}/${pokemon.url.split('/').slice(-2,-1)}.png`}
alt={pokemon.name}
/>
</Link>
<p>
<a href={pokemon.url} target="_blank" rel="noreferrer">
{pokemon.url}
</a>
</p>
</div>
))}
</div>
);

const renderPokemon = (pokemon: any) => (
<div>
<h1>{pokemon.name}</h1>
<img
src={`${imageUrl}/${pokemon.url.split('/').slice(-2,-1)}.png`}
alt={pokemon.name}
/>
</div>
)

if (naviagtion.state === 'loading') {
return <h1>Loading...</h1>
}


return selectedPokemon !== null ? renderPokemon(selectedPokemon) : renderPokedex();
}

Here we can see we access the pokemonId param with useParams and default to 0 if it is not present. This id allows us to set a state value for selectedPokemon which will determine if we show the entire pokedex or the single pokedex entry for the pokemonId in our path (renderPokedex vs. renderPokemon).

Conclusion

In this article we learned how to add routes to our React app, we started off creating basic routes and sub-routes. We learned how to create nested routes and how to use <Outlet/> as a placeholder for sub-route content. Then, we added routes which pre-load data to be used by the component and discussed how to bring in url params using react hooks to determine how our component should be rendered.

Appendix

Below are some extra files you may need to fully re-produce the example

index.css

.Contacts ul {
list-style: none;
margin: 0;
margin-bottom: 20px;
padding: 0;
}

.Contacts ul li {
padding: 10px;
}

.Contacts a {
color: #555;
text-decoration: none;
}

.Contacts a:hover {
color: #ccc;
text-decoration: none;
}

.App {
text-align: center;
}

.App ul.menu {
margin: 50px;
padding: 0;
list-style: none;
}

.App ul.menu li {
display: inline-block;
padding: 0 10px;
}

.App ul.menu li a {
color: #333;
text-decoration: none;
}

.App ul.menu li a:hover {
color: #ccc;
}

Home.tsx

export const Home = () => (
<div className="Home">
<h1>Home</h1>
</div>
)

--

--

Matthew MacFarquhar
Matthew MacFarquhar

Written by Matthew MacFarquhar

I am a software engineer working for Amazon living in SF/NYC.

No responses yet