Skip to content

Commit 0a517b6

Browse files
committed
New toys c:
1 parent 07aae1a commit 0a517b6

23 files changed

+1819
-86
lines changed

.prettierrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"trailingComma": "none"
3+
}

examples/aff/src/Example.purs

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,61 +5,89 @@ import Data.Foldable (find)
55
import Data.Maybe (Maybe(..))
66
import Data.Newtype (class Newtype, un)
77
import Effect (Effect)
8-
import Effect.Aff (Aff, Milliseconds(..), delay, error, throwError)
8+
import Effect.Aff (Aff, Milliseconds(..), delay, error, message, throwError)
99
import React.Basic.DOM as R
10+
import React.Basic.DOM.Events (capture_)
1011
import React.Basic.Events (handler_)
1112
import React.Basic.Hooks (type (/\), ReactComponent, Hook, JSX, component, element, fragment, useState, (/\))
1213
import React.Basic.Hooks as React
1314
import React.Basic.Hooks.Aff (useAff)
15+
import React.Basic.Hooks.ErrorBoundary (mkErrorBoundary)
1416

1517
mkExample :: Effect (ReactComponent {})
1618
mkExample = do
19+
errorBoundary <- mkErrorBoundary "AffExErrorBoundary"
1720
-- A component for fetching and rendering a Cat entity.
1821
catDetails <- mkCatDetails
1922
component "AffEx" \props -> React.do
20-
catKey /\ catChooser <- useCatKeyChooser
23+
catKey /\ reset /\ catChooser <- useCatKeyChooser
2124
pure
22-
$ R.div
23-
{ style: R.css { display: "flex", flexFlow: "column" }
24-
, children:
25-
[ R.h2_ [ R.text "Cat chooser" ]
26-
, R.p_
27-
[ R.text
28-
$ "Select a key to fetch! If you get bored (how would you even!?) "
29-
<> "try holding your arrow keys to select really fast! The result "
30-
<> "always matches the chosen key."
25+
$ R.div_
26+
[ R.h2_ [ R.text "Cat chooser" ]
27+
, errorBoundary \{ error, dismissError } -> case error of
28+
Just e ->
29+
fragment
30+
[ R.p_ [ R.text "Error!" ]
31+
, R.p_ [ R.text $ message e ]
32+
, R.button
33+
{ onClick: capture_ do reset *> dismissError
34+
, children: [ R.text "Reset" ]
35+
}
3136
]
32-
, catChooser
33-
, R.p_
34-
[ case catKey of
35-
Nothing -> mempty
36-
Just k -> element catDetails { catKey: k }
37+
Nothing ->
38+
fragment
39+
[ R.p_
40+
[ R.text
41+
$ "Select a key to fetch! If you get bored (how would you even!?) "
42+
<> "try holding your arrow keys to select really fast! The result "
43+
<> "always matches the chosen key."
44+
]
45+
, catChooser
46+
, R.p_
47+
[ case catKey of
48+
Nothing -> mempty
49+
Just k -> element catDetails { catKey: k }
50+
]
3751
]
38-
]
39-
}
52+
]
4053
where
4154
-- This hook packages up some interactive UI and the current
4255
-- selection the user has made via that UI.
43-
useCatKeyChooser :: Hook _ ((Maybe (Key Cat)) /\ JSX)
56+
useCatKeyChooser :: Hook _ ((Maybe (Key Cat)) /\ Effect Unit /\ JSX)
4457
useCatKeyChooser = React.do
4558
catKey /\ setCatKey <- useState Nothing
59+
let
60+
reset = setCatKey \_ -> Nothing
4661
let
4762
catChoice k =
48-
R.label_
49-
[ R.input
50-
{ type: "radio"
51-
, name: "cat-key"
52-
, checked: Just k == catKey
53-
, onChange:
54-
handler_ do
55-
setCatKey \_ -> Just k
56-
}
57-
, R.text $ " " <> showCatKey k
63+
R.div_
64+
[ R.label_
65+
[ R.input
66+
{ type: "radio"
67+
, name: "cat-key"
68+
, checked: Just k == catKey
69+
, onChange:
70+
handler_ do
71+
setCatKey \_ -> Just k
72+
}
73+
, R.text $ " " <> showCatKey k
74+
]
5875
]
5976

6077
showCatKey :: Key Cat -> String
6178
showCatKey (Key k) = "Cat " <> k
62-
pure $ catKey /\ fragment (map (catChoice <<< key) fakeDb)
79+
80+
catChooser =
81+
fragment
82+
( map (catChoice <<< key)
83+
( fakeDb
84+
<> [ Entity
85+
(Key "error (choose to throw a React render error)")
86+
(Cat { name: "" })
87+
]
88+
)
89+
)
90+
pure $ catKey /\ reset /\ catChooser
6391

6492
-- Hooks can't be used conditionally but components can!
6593
-- Not needing to deal with a `Maybe` key simplifies this

examples/suspense/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
output
2+
html/index.js
3+
package-lock.json
4+
node_modules

examples/suspense/Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
parent_sources := $$(npx spago -x ../../spago.dhall sources | sed 's/^/..\/..\//' | tr '\n' ' ')
2+
3+
all: output node_modules
4+
npx purs bundle -m Main --main Main output/*/*.js > output/bundle.js
5+
npx browserify output/bundle.js -o html/index.js
6+
7+
output: node_modules
8+
set -o noglob && \
9+
npx purs compile \
10+
src/**/*.purs \
11+
$(parent_sources)
12+
13+
node_modules:
14+
npm i --no-save browserify react react-dom
15+
16+
clean:
17+
rm -rf node_modules output
18+
19+
.PHONY: all output clean

examples/suspense/html/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>react-basic example</title>
5+
</head>
6+
<body>
7+
<div id="container"></div>
8+
<script src="index.js"></script>
9+
</body>
10+
</html>

examples/suspense/src/Example.purs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
module Example where
2+
3+
import Prelude
4+
import Data.Foldable (find)
5+
import Data.Maybe (Maybe(..))
6+
import Data.Newtype (class Newtype, un)
7+
import Data.Time.Duration (Seconds(..), fromDuration)
8+
import Effect (Effect)
9+
import Effect.Aff (Aff, Milliseconds(..), delay, error, message, throwError)
10+
import React.Basic.DOM as R
11+
import React.Basic.DOM.Events (capture_)
12+
import React.Basic.Events (handler_)
13+
import React.Basic.Hooks (type (/\), Hook, JSX, ReactComponent, component, element, fragment, useState, (/\))
14+
import React.Basic.Hooks as React
15+
import React.Basic.Hooks.ErrorBoundary (mkErrorBoundary)
16+
import React.Basic.Hooks.Suspense (Suspended, suspend, suspense)
17+
import React.Basic.Hooks.Suspense.Store (SuspenseStore, get, mkSuspenseStore)
18+
19+
mkExample :: Effect (ReactComponent {})
20+
mkExample = do
21+
catStore :: SuspenseStore (Key Cat) (Entity Cat) <-
22+
mkSuspenseStore (Just $ fromDuration $ Seconds 10.0) fetch
23+
let
24+
getCat = get catStore
25+
errorBoundary <- mkErrorBoundary "SuspenseExErrorBoundary"
26+
-- A component for fetching and rendering a Cat entity.
27+
catDetails <- mkCatDetails
28+
component "SuspenseEx" \props -> React.do
29+
catKey /\ reset /\ catChooser <- useCatKeyChooser
30+
pure
31+
$ R.div_
32+
[ R.h2_ [ R.text "Cat chooser" ]
33+
, errorBoundary \{ error, dismissError } -> case error of
34+
Just e ->
35+
fragment
36+
[ R.p_ [ R.text "Error!" ]
37+
, R.p_ [ R.text $ message e ]
38+
, R.button
39+
{ onClick: capture_ do reset *> dismissError
40+
, children: [ R.text "Reset" ]
41+
}
42+
]
43+
Nothing ->
44+
fragment
45+
[ R.p_
46+
[ R.text
47+
$ "Select a key to fetch! If you get bored (how would you even!?) "
48+
<> "try holding your arrow keys to select really fast! The result "
49+
<> "always matches the chosen key."
50+
]
51+
, catChooser
52+
, R.p_
53+
[ case catKey of
54+
Nothing -> mempty
55+
Just k ->
56+
suspense
57+
{ fallback: R.text "Loading..."
58+
, children: [ catDetails (getCat k) ]
59+
}
60+
]
61+
]
62+
]
63+
where
64+
-- This hook packages up some interactive UI and the current
65+
-- selection the user has made via that UI.
66+
useCatKeyChooser :: Hook _ ((Maybe (Key Cat)) /\ Effect Unit /\ JSX)
67+
useCatKeyChooser = React.do
68+
catKey /\ setCatKey <- useState Nothing
69+
let
70+
reset = setCatKey \_ -> Nothing
71+
let
72+
catChoice k =
73+
R.div_
74+
[ R.label_
75+
[ R.input
76+
{ type: "radio"
77+
, name: "cat-key"
78+
, checked: Just k == catKey
79+
, onChange:
80+
handler_ do
81+
setCatKey \_ -> Just k
82+
}
83+
, R.text $ " " <> showCatKey k
84+
]
85+
]
86+
87+
showCatKey (Key k) = "Cat " <> k
88+
89+
catChooser =
90+
fragment
91+
( map (catChoice <<< key)
92+
( fakeDb
93+
<> [ Entity
94+
(Key "error (choose to throw a React render error)")
95+
(Cat { name: "" })
96+
]
97+
)
98+
)
99+
pure $ catKey /\ reset /\ catChooser
100+
101+
-- Hooks can't be used conditionally but components can!
102+
-- Not needing to deal with a `Maybe` key simplifies this
103+
-- compoennt a bit.
104+
mkCatDetails :: Effect (Suspended (Entity Cat) -> JSX)
105+
mkCatDetails =
106+
map (\c -> element c <<< { getCat: _ }) do
107+
component "CatDetails" \{ getCat } -> React.do
108+
catState <- suspend getCat
109+
pure case entity catState of
110+
Cat { name } -> R.text $ "A cat named " <> name
111+
112+
-- Typed keys are a great way to tie entity-specific behavior
113+
-- to an ID. We can use this phantom type to write a class
114+
-- for generic, type-safe data fetching.
115+
newtype Key entity
116+
= Key String
117+
118+
derive instance eqKey :: Eq (Key entity)
119+
120+
derive instance ordKey :: Ord (Key entity)
121+
122+
derive instance ntKey :: Newtype (Key entity) _
123+
124+
-- An entity wrapper. In a real app this would hold other metadata
125+
-- such as create and update dates.
126+
data Entity entity
127+
= Entity (Key entity) entity
128+
129+
key :: forall entity. Entity entity -> Key entity
130+
key (Entity k _) = k
131+
132+
entity :: forall entity. Entity entity -> entity
133+
entity (Entity _ e) = e
134+
135+
class Fetch entity where
136+
fetch :: Key entity -> Aff (Entity entity)
137+
138+
-- An example entity
139+
newtype Cat
140+
= Cat { name :: String }
141+
142+
fakeDb :: Array (Entity Cat)
143+
fakeDb =
144+
[ Entity (Key "abc") (Cat { name: "Herb" })
145+
, Entity (Key "def") (Cat { name: "Maxi" })
146+
, Entity (Key "ghi") (Cat { name: "Chloe" })
147+
]
148+
149+
instance fetchCat :: Fetch Cat where
150+
fetch k = do
151+
delay $ Milliseconds 300.0
152+
-- pretend this happens on the server
153+
case fakeDb # find (key >>> (_ == k)) of
154+
Nothing ->
155+
-- This should never happen in a normal application path
156+
-- if only the server can generate keys :)
157+
throwError
158+
$ error
159+
$ "DB error: Cat not found for key "
160+
<> un Key k
161+
Just e -> pure e

examples/suspense/src/Main.purs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module Main where
2+
3+
import Prelude
4+
import Data.Maybe (Maybe(..))
5+
import Effect (Effect)
6+
import Effect.Exception (throw)
7+
import Example (mkExample)
8+
import React.Basic.Hooks (element)
9+
import React.Basic.DOM (render)
10+
import Web.DOM.NonElementParentNode (getElementById)
11+
import Web.HTML (window)
12+
import Web.HTML.HTMLDocument (toNonElementParentNode)
13+
import Web.HTML.Window (document)
14+
15+
main :: Effect Unit
16+
main = do
17+
container <- getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
18+
case container of
19+
Nothing -> throw "Container element not found."
20+
Just c -> do
21+
ex <- mkExample
22+
let
23+
app = element ex {}
24+
render app c

0 commit comments

Comments
 (0)