Normally in Router Component of Halogen, you would have a Query
to change route like this:
-- Router.purs
Goto page n -> do
S.log $ show page
handlePage page
pure n
HandleHeroListPage msg n -> do
case msg of
HeroList.ChangeRoute page -> do
handlePage page
pure n
_ ->
pure n
The handlePage function would look something like this:
-- Router.purs
handlePage :: Page -> _
handlePage page = do
currentPage <- H.gets (\s -> s.currentPage)
case (currentPage == page) of
true ->
pure unit
false -> do
H.modify_ (_ { currentPage = page })
pure unit
The problem with this is that to change route we will have to call HE.onClick $ HE.input_ HeroListPage
when using in Router Component or when using in child component we would pass a message back to the parent component H.raise $ ChangeRoute HeroListPage
and then we handle it like in HandleHeroListPage
above.
When changing route by the deep nested component, we would have to pass some kind of messages back to the top component(Router Component) to handle and this could be very annoying.
A faster way is to simply change route with href="/#/home"
but using the hash style url is problemtic for search engine as well as google analytic.
Using href without hash href="/home"
will reload the page and we do not want this. So that is why we have to change url by changing url manually using pushState.
Global Passing Message can be really handy in this situation, we can pass message directly to Router Component from any child component by using Avar within a AppM ReaderT Pattern.
Our AppM will look like this:
-- AppM.purs
newtype AppM a = AppM (ReaderT Env Aff a)
runAppM :: forall a. Env -> AppM a -> Aff a
runAppM e (AppM m) = runReaderT m e
-- Data.Env.purs
data GlobalMessage
= LogoutG
| NavigateG Page
type Env =
{ globalMessage :: AVar GlobalMessage
, pushStateInterface :: PushStateInterface
}
-- Main.purs
main :: Effect Unit
main = do
log "Purescript is starting..."
HA.runHalogenAff do
globalMessage <- AVar.empty
pushStateInterface <- liftEffect $ RoutingP.makeInterface
let
environment :: Env
environment = { pushStateInterface, globalMessage}
rootComponent :: H.Component HH.HTML Router.Query Router.Input Void Aff
rootComponent = H.hoist (runAppM environment) Router.component
-- ...
Inside our Router, we will fork a Query that listen to AVar changes
Init n -> do
S.log "Router Initialized"
void $ H.fork $ eval $ ListenForGlobalMessage n
pure n
ListenForGlobalMessage n -> do
globalMessage <- asks _.globalMessage
query <- H.liftAff $ AVar.take globalMessage
case query of
NavigateG page -> do
handlePage page
pure unit
_-> do
pure unit
eval (ListenForGlobalMessage n)
From within any child component, we call raiseG
which basically put a new global message inside avar
raiseG query = do
globalMessage <- asks _.globalMessage
H.liftAff $ AVar.put query globalMessage
Example inside a child component call HeroList, we just call raiseG with the message we want to pass to Router Component directly
GotoHeroDetail n -> do
S.raiseG $ NavigateG $ HeroDetailPage 1
pure n
That's it, you can talk a look at fully working example inside my haloge starter project: https://github.com/rinn7e/rinn7e-halogen-starter
Thanks for reading!
Top comments (0)