Skip to content

katsujukou/purescript-halogen-router

Repository files navigation

Halogen Router

CI

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-routing
  • purescript-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