Introducing Routing to a React Web App
A Noob Meets Web Apps, Episode 5
In previous episode I structured my React app using composition of components. Now it’s time to adopt react-router to go from poll list to single poll where user will vote.
$ npm install --save react-router-dom
$ npm install --save-dev @types/react-router-dom
Firebase hosting is already configured to support browser history thanks to global rewrites:
Make first route
I want to make a route to highlighted polls, parametrizing language. I move HighlightedPolls.tsx
to app/features/highlightedPolls/component.tsx
defining a new module. So I can create routeInfo.ts
inside that folder:
If I can detect a valid language in route path I will load highlighted polls in that language; otherwise, I would like router to redirect to current browser language. To do so, I choose again composition by defining a I18n
module exposing two components:
I18nFromMatch
component uses I18n
component to render a successfully matched language. When language can’t be identified, it renders via else()
prop.
Now I can create app/features/highlightedPolls/route.tsx
to encapsulate a <Route>
valid to show requested polls:
Finally I can define BrowserRouter
routes inside App.tsx
. <Route>
without path is rendered (and, so, it redirects) when first one fails to match, thanks to <Switch>
:
It seems a little overkill to ha a separated RouteInfo
object: I’ll definitely merge pattern
informations inside HighlightedPolls.Route
object before to go on.
Don’t forget who I am
I am a noob. While this approach works, it is not correct. <Switch>
expects all children to be <Route>
or <Redirect>
components. Clearly HighlightedPolls.Route
should not be put there: instead, I will define a function that returns a proper react-router route:
The mechanic is very similar, but I use HighlightedPollse.route()
function to create an already configured <Route>
.
Routing to poll detail
We need a component to display choices inside a poll. That will be the place where user could vote.
Firstly, we create a module in app/features/poll
, like we have done with highlighted polls. We define a gateway to retrieve poll from API, a cell to display each choice detail and a main component that displays items in a grid:
Note that Columns
is activated also on mobile devices and, in those environments, each column will be sized to 50% of available width.
I create also a route.tsx
like I did for highlighted polls. But, first of all, I need to move <API>
and <I18nFromMatch>
out of HighlightedPolls.route()
. That’s because also Poll.route()
will need that data, so it’s preferable to move this state up.
I18FromMatch
can be easily substituted by I18n.route()
:
HighlightedPolls.route()
can now be streamlined to a very simple function:
The same concept can be applied to Poll.route()
:
Resulting routing structure in App.tsx
is self-explainatory. Redirecting functions are just functions that return <Redirect>
:
I18n.route()
usage is a bit awkward because the double render
prop. I can change approach by exporting a function that returns all routes that lead to a supported language. When URL contains an unsupported language <Switch>
mechanics will render a <Redirect>
to browser current language:
Drill down from polls to poll
The final step is to connect poll list to single poll detail. It should be trivial using <Link>
component provided by react-router library. I’d like to make a step further by decoupling components from start: I don’t want Polls
component to know nothing about routing to Poll
.
I’ll introduce the generic concept of wrapping in features/polls/component.tsx
:
HighlightedPolls
still ignore existence of Poll
component but it knows about routing, so I can use <Link>
component:
Now I can add a function to features/poll/route.tsx
in order to provide a way to generate route paths to reach single polls:
This function will be adopted inside App.tsx
:
Links work and components are still decoupled: great!
Preserving Poll
object
When you load Poll.route()
you fire a fetch to download Poll
object from API. This is a bit an overkill since we already have a Poll
object stored inside Polls
component.
We need to pass also Poll
object, if available, from features/polls/component.tsx
:
We need to provide a function to create link location in features/poll/route.tsx
. When you find a poll inside location state, it is not necessary to fetch data from API.
Adaptation to features/highlightedPolls/component.tsx
is straightforward. Finally we just need to make HighlightedPolls.route()
to use Poll.locationDescriptor()
:
It remains only to merge everything in App.tsx
, in route definitions:
Now, when you click on a poll, you are routed to detail without fetching; if you come directly (e.g.: by pasting URL), fetch is performed as before.