Halogen Router
February 27, 2022 ยท View on GitHub
Routing management for Halogen.
TODO
- Add some informative comments.
- Add tests.
Installation
Install halogen-router with Spago:
spago install halogen-router
Quick Start
This library is the one-stop-shop for parsing, printing, navigating and subscribing to changes of the web application routes.
This library is merely a wrapper layer to following great libraries:
purescript-routingpurescript-routing-duplex
Defining the routes as purescript type.
First of all, we should define the routes of our cool web app as standard PureScript type.
We also create the codec for our routes using the combinator from purescript-routing-duplex.
If you aren't familiar with that library, please refer to the document.
module App.Data.Route where
data MyRoute
= Home
| About
derive instance Eq MyRoute
derive instance Generic MyRoute _
routeCodec :: RouteDuplex' MyRoute
routeCodec = root $ sum
{ "Home": noArgs
, "About": "about" / noArgs
}
Using the router
Next, we add the MonadRouter r m constraint on the underlying monad of the Halogen component, with its first type parameter specified to our MyRoute type:
import App.Route (MyRoute)
import Halogen.Router.Class (class MonadRouter)
component
:: forall q i o m
. MonadRouter MyRoute m
=> H.Component q i o m
Now we are ready to use the router functionality witin this component!
To do this, we can utilize those methods provided by the MonadRoute typeclass, but more simple and recommended way is to call the useRouter hook.
The usRouter hook returns the Maybe MyRoute value paired with the RouterFn utilities:
import Halogen.Hooks as Hooks
import Halogen.Router.PushState.UseRouter (useRouter)
component
:: forall q i o m
. MonadRouter MyRoute m
=> H.Component q i o m
component = Hooks.component \_ _ -> Hooks.do
current /\ routerFn <- useRouter
With this, if current browser location's path (or hash for hash-based routing) matches with one of the defined routes, then the value of current is the decoded MyRoute wrapped in the Just constructor, and if matches none of the routes, then Nothing. When browser location changed, current value also changes reactively.
The routerFn is the record containing two router functinality:
type RouterFn =
{ navigate :: MyRoute -> HookM m Unit -- update the browser location to the specified route
, print :: MyRoute -> HookM m String -- print the `MyRoute` value as the path string.
}
Running the application
When we run the application, we'll transform the underlying monad, which satisfies the MonadRouter constraint, to the Aff monad.
To achieve this, we can define our application-specific monad using the RouterT transformer:
import Halogen.Router.Trans.Hash (RouterT) -- For hash-based routing
import Halogen.Router.Trans.PushState (RouterT) -- For PushState-based routing
newtype AppM a = AppM (RouterT Aff a)
This monad can be easily transformed to the Aff using runRouterT.
Of course, you can add as many transformer layers to the stack as you need.
The runRouterT require the router instance.
We can create the router instance by providing the RouteDuplex route codec to the mkRouter function. Here is the example:
import Halogen as H
import Halogen.Router.Trans.PushState (mkRouter, runRouter)
import App.Component.App (app) -- our application's root component
main :: Effect Unit
main = runHalogenAff do
body <- awaitBody
router <- mkRouter routeCodec
let rootComponent = H.hoist (runRouterT router) app
runUI rootComponent {} body