Skip to content

Commit d7c1c16

Browse files
committed
Add async component
1 parent 10f2542 commit d7c1c16

File tree

8 files changed

+178
-0
lines changed

8 files changed

+178
-0
lines changed

examples/async/.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/async/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
all: node_modules
2+
purs compile src/*.purs '../../src/**/*.purs' '../../bower_components/purescript-*/src/**/*.purs'
3+
purs bundle -m Main --main Main output/*/*.js > output/bundle.js
4+
node_modules/.bin/browserify output/bundle.js -o html/index.js
5+
6+
node_modules:
7+
npm install
8+

examples/async/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Async Counter Example
2+
3+
## Building
4+
5+
```
6+
npm install
7+
make all
8+
```
9+
10+
This will compile the PureScript source files, bundle them, and use Browserify to combine PureScript and NPM sources into a single bundle.
11+
12+
Then open `html/index.html` in your browser.

examples/async/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/async/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"dependencies": {
3+
"react": "16.6.0",
4+
"react-dom": "16.6.0"
5+
},
6+
"devDependencies": {
7+
"browserify": "16.2.3"
8+
}
9+
}

examples/async/src/AsyncCounter.purs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
module AsyncCounter where
2+
3+
import Prelude
4+
5+
import Effect.Aff (Milliseconds(..), delay)
6+
import Effect.Class (liftEffect)
7+
import Effect.Console (log)
8+
import React.Basic (Component, JSX, StateUpdate(..), capture_, createComponent, fragment, keyed, make)
9+
import React.Basic.Components.Async (asyncWithLoader)
10+
import React.Basic.DOM as R
11+
12+
component :: Component Props
13+
component = createComponent "AsyncCounter"
14+
15+
type Props =
16+
{ label :: String
17+
}
18+
19+
data Action
20+
= Increment
21+
22+
asyncCounter :: Props -> JSX
23+
asyncCounter = make component { initialState, update, render }
24+
where
25+
initialState = { counter: 0 }
26+
27+
update self = case _ of
28+
Increment ->
29+
Update self.state { counter = self.state.counter + 1 }
30+
31+
render self =
32+
fragment
33+
[ R.p_ [ R.text "Notes:" ]
34+
, R.ol_
35+
[ R.li_ [ R.text "The two counts should never be out of sync" ]
36+
, R.li_ [ R.text "\"done\" should only be logged to the console once for any loading period (in-flight requests get cancelled as the next request starts)" ]
37+
]
38+
, R.button
39+
{ onClick: capture_ self Increment
40+
, children: [ R.text (self.props.label <> ": " <> show self.state.counter) ]
41+
}
42+
, R.text " "
43+
, keyed (show self.state.counter) $
44+
asyncWithLoader (R.text "Loading...") do
45+
liftEffect $ log "start"
46+
delay $ Milliseconds 2000.0
47+
liftEffect $ log "done"
48+
pure $ R.text $ "Done: " <> show self.state.counter
49+
]
50+
51+

examples/async/src/Main.purs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module Main where
2+
3+
import Prelude
4+
5+
import AsyncCounter (asyncCounter)
6+
import Data.Maybe (Maybe(..))
7+
import Effect (Effect)
8+
import Effect.Exception (throw)
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 ->
21+
let app = asyncCounter { label: "Async Increment" }
22+
in render app c

src/React/Basic/Components/Async.purs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
module React.Basic.Components.Async
2+
( async
3+
, asyncWithLoader
4+
) where
5+
6+
import Prelude
7+
8+
import Data.Maybe (Maybe(..), fromMaybe)
9+
import Effect.Aff (Aff, Fiber, error, killFiber, launchAff, launchAff_)
10+
import Effect.Class (liftEffect)
11+
import React.Basic (Component, JSX, StateUpdate(..), createComponent, empty, make, send)
12+
13+
component :: Component (Aff JSX)
14+
component = createComponent "Async"
15+
16+
data FetchAction
17+
= ReplaceFiber (Fiber Unit)
18+
| UpdateJSX JSX
19+
20+
async :: Aff JSX -> JSX
21+
async = asyncWithLoader empty
22+
23+
asyncWithLoader :: JSX -> Aff JSX -> JSX
24+
asyncWithLoader loader = make component
25+
{ initialState
26+
, update
27+
, render
28+
, didMount: launch
29+
-- , didUpdate: No! Implementing `didUpdate` breaks the
30+
-- Aff/Component lifecycle relationship.
31+
, willUnmount: cleanup
32+
}
33+
where
34+
initialState =
35+
{ jsx: Nothing
36+
, pendingFiber: pure unit
37+
}
38+
39+
update { props, state } = case _ of
40+
ReplaceFiber newFiber ->
41+
UpdateAndSideEffects
42+
state { jsx = Nothing, pendingFiber = newFiber }
43+
\_ -> kill state.pendingFiber
44+
45+
UpdateJSX jsx ->
46+
Update
47+
state { jsx = Just jsx }
48+
49+
render self =
50+
fromMaybe loader self.state.jsx
51+
52+
launch self = do
53+
fiber <- launchAff do
54+
jsx <- self.props
55+
liftEffect $ send self $ UpdateJSX jsx
56+
send self $ ReplaceFiber fiber
57+
58+
cleanup self =
59+
kill self.state.pendingFiber
60+
61+
kill =
62+
launchAff_ <<< killFiber (error "Cancelled")

0 commit comments

Comments
 (0)