Skip to content

Commit 4295ec0

Browse files
committed
Some cleaning, added a readme and an example
1 parent 1302b66 commit 4295ec0

File tree

4 files changed

+137
-25
lines changed

4 files changed

+137
-25
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# purescript-wire-react-router
2+
3+
A basic pushstate router for React, with support for asynchronous routing logic. Built using [react-basic-hooks](https://github.com/spicydonuts/purescript-react-basic-hooks) and [wire](https://github.com/robertdp/purescript-wire). I recommend [routing-duplex](https://github.com/natefaubion/purescript-routing-duplex) for easy parsing and printing.
4+
5+
```purescript
6+
makeRouter ::
7+
forall route f.
8+
Foldable f =>
9+
{ interface :: PushStateInterface
10+
, fallback :: route
11+
, parse :: String -> f route
12+
, print :: route -> String
13+
, onRoute :: route -> Router route Transitioning Resolved Unit
14+
} ->
15+
Effect
16+
{ signal :: Signal (Route route)
17+
, component :: JSX
18+
, navigate :: route -> Effect Unit
19+
, redirect :: route -> Effect Unit
20+
}
21+
```
22+
23+
For a basic example see [`examples/RoutingDuplex.purs`](https://github.com/robertdp/purescript-wire-react-router/blob/master/examples/RoutingDuplex.purs).

examples/RoutingDuplex.purs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{-| This example also requires the following dependencies to be installed:
2+
- react-basic-dom
3+
- routing-duplex
4+
- wire-react
5+
-}
6+
module Example.RoutingDuplex where
7+
8+
import Prelude hiding ((/))
9+
import Data.Generic.Rep (class Generic)
10+
import Data.Lens as Lens
11+
import Effect (Effect)
12+
import React.Basic.DOM as R
13+
import React.Basic.Events (handler_)
14+
import React.Basic.Hooks (JSX)
15+
import React.Basic.Hooks as React
16+
import Routing.Duplex (RouteDuplex', default, end, parse, print, root)
17+
import Routing.Duplex.Generic (noArgs, sum)
18+
import Routing.Duplex.Generic.Syntax ((/))
19+
import Routing.PushState as PushState
20+
import Wire.React as Wire
21+
import Wire.React.Router (_Route)
22+
import Wire.React.Router as Router
23+
24+
data Route
25+
= Home
26+
| About
27+
| NotFound
28+
29+
derive instance genericRoute :: Generic Route _
30+
31+
routes :: RouteDuplex' Route
32+
routes =
33+
default NotFound
34+
$ root
35+
$ sum
36+
{ "Home": end noArgs
37+
, "About": "about" / end noArgs
38+
, "NotFound": "_" / "not-found" / end noArgs
39+
}
40+
41+
makeApp :: Effect (Unit -> JSX)
42+
makeApp = do
43+
interface <- PushState.makeInterface
44+
router <-
45+
Router.makeRouter
46+
{ interface
47+
, fallback:
48+
-- used as the initial route if the parser fails
49+
NotFound
50+
, parse: parse routes
51+
, print: print routes
52+
, onRoute:
53+
\_ ->
54+
-- this skips any async routing logic by accepting the parsed route immediately
55+
Router.continue
56+
}
57+
React.component "App" \props -> React.do
58+
route <-
59+
-- subscribe to the signal containing the current route
60+
Wire.useSignal router.signal
61+
pure
62+
$ React.fragment
63+
[ -- the router subscriber to pushstate events when this component is mounted, and unsubscribes when unmounted
64+
router.component
65+
, R.h1_
66+
[ R.text case Lens.view _Route route of
67+
Home -> "Home"
68+
About -> "About"
69+
NotFound -> "Not Found"
70+
]
71+
, R.div_
72+
[ R.text "Menu: "
73+
, R.button
74+
{ onClick: handler_ $ router.navigate Home
75+
, children: [ R.text "Go to Home page" ]
76+
}
77+
, R.button
78+
{ onClick: handler_ $ router.navigate About
79+
, children: [ R.text "Go to About page" ]
80+
}
81+
]
82+
]

spago.dhall

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
Welcome to a Spago project!
33
You can edit this file as you like.
44
-}
5-
{ name = "my-project"
5+
{ name = "wire-react-router"
66
, dependencies =
77
[ "aff"
8-
, "indexed-monad"
98
, "freet"
9+
, "indexed-monad"
1010
, "profunctor-lenses"
1111
, "react-basic-hooks"
1212
, "routing"

src/Router.purs

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Wire.React.Router
55

66
import Prelude
77
import Control.Monad.Free.Trans (runFreeT)
8-
import Data.Foldable (class Foldable, for_, traverse_)
8+
import Data.Foldable (class Foldable, for_)
99
import Data.Maybe (Maybe(..))
1010
import Effect (Effect)
1111
import Effect.Aff (error, killFiber, launchAff, launchAff_)
@@ -21,48 +21,55 @@ import Wire.React.Router.Control (Command, Resolved, Route(..), Router, Transiti
2121
import Wire.Signal (Signal)
2222
import Wire.Signal as Signal
2323

24-
makeRouter ::
25-
forall route f.
26-
Foldable f =>
27-
{ interface :: PushStateInterface
28-
, default :: route
29-
, decode :: String -> f route
30-
, encode :: route -> String
31-
, onRouteChange :: route -> Router route Transitioning Resolved Unit
32-
} ->
33-
Effect
34-
{ signal :: Signal (Route route)
24+
type Interface route
25+
= { signal :: Signal (Route route)
3526
, component :: JSX
3627
, navigate :: route -> Effect Unit
3728
, redirect :: route -> Effect Unit
3829
}
39-
makeRouter { interface, default, decode, encode, onRouteChange } =
30+
31+
makeRouter ::
32+
forall f route.
33+
Foldable f =>
34+
{ interface :: PushStateInterface
35+
, fallback :: route
36+
, parse :: String -> f route
37+
, print :: route -> String
38+
, onRoute :: route -> Router route Transitioning Resolved Unit
39+
} ->
40+
Effect (Interface route)
41+
makeRouter { interface, fallback, parse, print, onRoute } =
4042
let
41-
onPushState k = PushState.matchesWith decode (\_ -> k) interface
43+
onPushState k = PushState.matchesWith parse (\_ -> k) interface
4244

43-
navigate route = interface.pushState (unsafeToForeign {}) (encode route)
45+
navigate route = interface.pushState (unsafeToForeign {}) (print route)
4446

45-
redirect route = interface.replaceState (unsafeToForeign {}) (encode route)
47+
redirect route = interface.replaceState (unsafeToForeign {}) (print route)
4648
in
4749
do
48-
{ modify, signal } <- Signal.create (Transitioning Nothing default)
49-
-- replace the user-supplied default route with the current route, if possible
50-
interface.locationState >>= \{ path } -> for_ (decode path) \route -> modify \_ -> Transitioning Nothing route
50+
{ modify, signal } <- Signal.create (Transitioning Nothing fallback)
51+
do
52+
-- replace the user-supplied fallback route with the current route, if possible
53+
{ path } <- interface.locationState
54+
for_ (parse path) \route -> modify \_ -> Transitioning Nothing route
5155
fiberRef <- Ref.new Nothing
5256
previousRouteRef <- Ref.new Nothing
5357
let
5458
runRouter route = do
55-
-- if some previous long-running routing logic is still active, kill it
56-
Ref.read fiberRef >>= traverse_ (launchAff_ <<< killFiber (error "Transition cancelled"))
59+
do
60+
-- if some previous long-running routing logic is still active, kill it
61+
oldFiber <- Ref.read fiberRef
62+
for_ oldFiber \fiber -> launchAff_ (killFiber (error "Transition cancelled") fiber)
5763
previousRoute <- Ref.read previousRouteRef
64+
-- set the route state to "transitioning" with the previous successful route
5865
modify \_ -> Transitioning previousRoute route
5966
let
6067
finalise r =
6168
liftEffect do
6269
Ref.write (Just r) previousRouteRef
6370
modify \_ -> Resolved previousRoute r
6471
fiber <-
65-
launchAff case onRouteChange route of
72+
launchAff case onRoute route of
6673
Router router ->
6774
router
6875
# runFreeT \cmd -> do
@@ -75,6 +82,6 @@ makeRouter { interface, default, decode, encode, onRouteChange } =
7582
Ref.write (Just fiber) fiberRef
7683
component <-
7784
React.component "Wire.Router" \_ -> React.do
78-
React.useEffectOnce do onPushState runRouter
85+
React.useEffectOnce (onPushState runRouter)
7986
pure React.empty
8087
pure { signal, component: component unit, navigate, redirect }

0 commit comments

Comments
 (0)