Help with integrating Routing with React.Basic


#1

Hi, PureScript newbie here.

I’m starting out with React.Basic, and I’m a bit stuck trying to work out how to integrate Routing. I think I need to use the locationState from a Routing.PushState PushStateInterface, but I can’t work out where to run the Effect I need to do that. Can’t be in render in my top-level application component, right? So where? Do I pass in the route from the monadic main? But how do I listen for updates?

I’ve seen some helpful examples of doing Routing with Halogen, but React.Basic is a bit different.

Are there any examples of using these libraries together?

cheers,
Simon


#2

Well, I think I worked it out. Here’s what I did.

I called makeInterface from Routing.PushState at the top-level in main, and passed it through to my application component via props:

import Routing.PushState (makeInterface)

main :: Effect Unit
main = do
  root <- getElementById "root" =<< (map toNonElementParentNode $ document =<< window)
  psi <- makeInterface
  case root of
    Nothing -> throw "Root element not found."
    Just r  -> render (app { psi: psi }) r

In my application component, I keep the route in my state:

type State =
  { route :: Route
  }

initialState :: State
initialState =
  { route: Home
  }

and I have a Navigate action, which can be sent to the component to change the route:

app props = make component
  { initialState

  , update: \self -> case _ of
      Navigate route ->
        UpdateAndSideEffects
          self.state { route = route }
          \nextSelf -> do
            log $ "Navigate: " <> show route
            nextSelf.props.psi.pushState (unsafeToForeign unit) (show route)

For now, I have arranged for show to convert my Route to a string suitable for the path in push-state, but I’ll fix that shortly (I think there must be better ways to do that.)

Because my route is in the state of my top-level component, it’s easy to render based on it.

So I think I’m done. Comments welcome, in case any of this is a bit crass.


#3

Ah, one more thing - listening for location state changes. I register a listener in the application component didMount, like this:

  , didMount: \self -> do
    unlisten <- self.props.psi.listen \loc -> do
      log $ "location now " <> show loc.path
      case matchRoute loc.path of
        Left error -> log $ "failed to match route " <> loc.path <> ": " <> error
        Right r -> send self $ LocationChanged r

Here, matchRoute is simply match allRoutes where allRoutes :: Match Route is my parser for route matching.

And I needed a new action, since LocationChanged is clearly different from Navigate. And with this, I realized Navigate is the wrong place to set the route state in the component, as the LocationChanged action gets triggered as a side-effect of Navigate.

  , update: \self -> case _ of
      Navigate route ->
        SideEffects
          \nextSelf -> do
            log $ "Navigate: " <> show route
            nextSelf.props.psi.pushState (unsafeToForeign unit) (show route)
      LocationChanged route ->
        UpdateAndSideEffects
          self.state { route = route }
          \nextSelf -> do
            log $ "LocationChanged: " <> show route

I think that might be it now.