-
Notifications
You must be signed in to change notification settings - Fork 28
6) Routing
Erdapfel is a pure SPA, which is structured into several main features (ex : home, poi list, single poi, directions, etc.), each corresponding to a specific UI ('panel').
It uses a client-side routing mechanism to organize the navigation between these panels and ensure the consistency between the application state and the browser location management. It is crucial so that, like in a classic (page-by-page) web application:
- the application state at any moment corresponds to a unique URL, meaning the user can bookmark or reload the app and find it back in the same state ;
- the application reacts correctly to the user navigating in the history with built-in browser Back/Forward actions.
All client-side routers work on the same principles, and it's the case for Erdapfel:
- they organize the different application states into a hierarchy of HTTP routes with path and arguments (usually defined as URL regexps) ;
- they use the History API to manipulate the browser
history
object in two ways:- Link navigations internal to the app are not made as HTTP requests to the server but intercepted and resolved as
pushState
orreplaceState
actions, to create real history entries for the browser. - The
popstate
event is listened to, to detect direct history manipulations through Back/Forward.
- Link navigations internal to the app are not made as HTTP requests to the server but intercepted and resolved as
- when one of these two events occur (or when the app loads), the new active history entry (an URL + an optional state object) is checked through the routes to determine which action to perform on the app (usually changing what to render).
The current Erdapfel router was first introduced in https://github.com/Qwant/erdapfel/pull/322 as a custom and very basic implementation to solve the most immediate URL management problems the app had. It hasn't evolved much since. Even during the migration to React it was mostly untouched, being used normally from React components or the Vanilla JS code.
It's made of the following parts:
-
app_router.js defines
Router
, a class with two methods:-
addRoute
, to declare routes as regexp/callback pairs (there is also a name which is only for self documentation during declaration) -
routeUrl
, to check a url against the declared routes and trigger the corresponding callback if a route matches
-
-
app_panel.js, the client app entry point
- instantiates a
Router
object - listens to
popstate
events to callrouteUrl
on theRouter
instance when it happens - defines mostly
navigateTo
to callrouteUrl
explicitely on theRouter
instance (It's attached to thewindow.app
global object to be available easily for all the app, but it would be better to scope it in its module) - passes the router instance as a prop to the
RootComponent
, which in turn passes it toPanelManager
- instantiates a
- PanelManager.js is where the app routes are declared and where for each one we change some React state so what is rendered in the app changes.
- When a component wants to navigate to another part of the app, it needs to call
window.app.navigateTo
with the URL corresponding to the target state.
The current router implementation wasn't made to last, but it did. It works fine most of the time but still has some limitations:
-
the bits in
app_panel.js
are a bit clumsy and should be packaged more properly, first not relying on globals. Also, relative URL and query string management relies on a bit of other functions.Maybe React hooks could help making all that cleaner.
-
it's really not React-ish, being more imperative than declarative.
-
related, real links (
<a>
) are not currently managed.window.app.navigateTo
is mostly called as result of button clicks, where in a lot of places a link with an explicithref
attribute would be the right thing to use.As a result, the code is more complex than needed, accessibility sucks, you can't open in a new tab or see the destination url in the status bar. In theory it's not so hard to manage by intercepting clicks on
a
elements on the whole document, but it's another moving part… -
it's not made to be used on server-side, which could be a limit if we want to do some SSR.
A future-proof alternative would be to switch to React Router:
- instead of a custom and fragile implementation, it's a robust solution, almost a de-facto standard, used by many project (notably Qwant Search)
- it would greatly simplify the top parts of the app, and allow to focus on the attributes in each concerned components
- real links are natively managed through
<Link>
components. - fully made for SSR
An unfinished migration POC is available in https://github.com/Qwant/erdapfel/pull/1175, which shows most needed steps.
The hard thing is that the migration is hard or impossible to do progressively, it must be a one-step task, so it's no easy stuff.