The second argument of
liveApp is an
Effect monad which returns a
Response. We will rarely return a
Response directly, so we use the
runPage function to turn
Page es '[...] into an
Eff es Response
The idea is that an application is divided into
independent Pages, which completely reload when you navigate between them. This is a deliberate choice to simplify development. In Single Page Applications, internal navigation often causes all sorts of strange state synchronization issues.
We suggest you create a module for each
Page, each with its own
page function
page :: Page es [Message, Counter]
page = do
pure $ do
hyper Message1 $ messageView "Hello"
hyper Message2 $ messageView "World"
hyper Counter $ viewCount 0
Since we have more than one
Page, we need a way to choose between them. You could create a manual function
Hyperbole :> es => Eff es Response which reads the
Request, and
runPage different
Pages depending on the
Path, but Hyperbole comes with support for type-safe
Routes:
data AppRoute
= Main
| Messages
| User UserId
deriving (Eq, Generic)
instance Route AppRoute where
baseRoute = Just Main
>>> routeUri Main
"/"
>>> routeUri (User 3)
"/user/3"
Then, as the second argument to
liveApp, you can pattern match on the
Route to run various
Pages using
routeRequest. If your route has data in it, you can pass it to the corresponding page
router :: (Hyperbole :> es) => AppRoute -> Eff es Response
router Messages = runPage Messages.page
router (User cid) = runPage $ Users.page cid
router Main = do
pure $ view $ do
el "click a link below to visit a page"
route Messages "Messages"
route (User 1) "User 1"
route (User 2) "User 2"
app :: Application
app = liveApp (document documentHead) (routeRequest router)
Your application will want to support various side effects. It's helpful to create a single function that runs all shared effects:
runApp :: (Hyperbole :> es, IOE :> es) => AppConfig -> Eff (Reader AppConfig : Concurrent : es) a -> Eff es a
runApp config = runConcurrent . runReader config
Add all your effects by calling your runner:
app' :: AppConfig -> Application
app' config = liveApp (document documentHead) (runApp config $ routeRequest router')
A few pages might use effects that the rest don't need, or a specific implementation. You can choose to run an effect for a single page
router' :: (Hyperbole :> es, Concurrent :> es) => AppRoute -> Eff es Response
router' Messages = runReader @Text "Secret Message!" $ runPage SideEffects.page
router' (User cid) = runPage $ Users.page cid
router' Main = pure $ view "..."