Skip to content

Commit

Permalink
Add async component (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
spicydonuts authored Nov 9, 2018
1 parent 7c19e84 commit 9999161
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 6 deletions.
4 changes: 4 additions & 0 deletions examples/async/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output
html/index.js
package-lock.json
node_modules
8 changes: 8 additions & 0 deletions examples/async/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
all: node_modules
purs compile src/*.purs '../../src/**/*.purs' '../../bower_components/purescript-*/src/**/*.purs'
purs bundle -m Main --main Main output/*/*.js > output/bundle.js
node_modules/.bin/browserify output/bundle.js -o html/index.js

node_modules:
npm install

12 changes: 12 additions & 0 deletions examples/async/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Async Counter Example

## Building

```
npm install
make all
```

This will compile the PureScript source files, bundle them, and use Browserify to combine PureScript and NPM sources into a single bundle.

Then open `html/index.html` in your browser.
10 changes: 10 additions & 0 deletions examples/async/html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>react-basic example</title>
</head>
<body>
<div id="container"></div>
<script src="index.js"></script>
</body>
</html>
9 changes: 9 additions & 0 deletions examples/async/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"dependencies": {
"react": "16.6.0",
"react-dom": "16.6.0"
},
"devDependencies": {
"browserify": "16.2.3"
}
}
51 changes: 51 additions & 0 deletions examples/async/src/AsyncCounter.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module AsyncCounter where

import Prelude

import Effect.Aff (Milliseconds(..), delay)
import Effect.Class (liftEffect)
import Effect.Console (log)
import React.Basic (Component, JSX, StateUpdate(..), capture_, createComponent, fragment, keyed, make)
import React.Basic.Components.Async (asyncWithLoader)
import React.Basic.DOM as R

component :: Component Props
component = createComponent "AsyncCounter"

type Props =
{ label :: String
}

data Action
= Increment

asyncCounter :: Props -> JSX
asyncCounter = make component { initialState, update, render }
where
initialState = { counter: 0 }

update self = case _ of
Increment ->
Update self.state { counter = self.state.counter + 1 }

render self =
fragment
[ R.p_ [ R.text "Notes:" ]
, R.ol_
[ R.li_ [ R.text "The two counts should never be out of sync" ]
, 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)" ]
]
, R.button
{ onClick: capture_ self Increment
, children: [ R.text (self.props.label <> ": " <> show self.state.counter) ]
}
, R.text " "
, keyed (show self.state.counter) $
asyncWithLoader (R.text "Loading...") do
liftEffect $ log "start"
delay $ Milliseconds 2000.0
liftEffect $ log "done"
pure $ R.text $ "Done: " <> show self.state.counter
]


22 changes: 22 additions & 0 deletions examples/async/src/Main.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Main where

import Prelude

import AsyncCounter (asyncCounter)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Exception (throw)
import React.Basic.DOM (render)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
import Web.HTML.HTMLDocument (toNonElementParentNode)
import Web.HTML.Window (document)

main :: Effect Unit
main = do
container <- getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
case container of
Nothing -> throw "Container element not found."
Just c ->
let app = asyncCounter { label: "Async Increment" }
in render app c
8 changes: 2 additions & 6 deletions src/React/Basic.purs
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,6 @@ foreign import createComponent
. String
-> Component props

-- | A simplified alias for `ComponentSpec`. This type is usually used to represent
-- | the default component type returned from `createComponent`.
-- type Component props = forall state action. ComponentSpec props state action

-- | Opaque component information for internal use.
-- |
-- | __*Note:* Never define a component with
Expand Down Expand Up @@ -291,7 +287,7 @@ foreign import make
:: forall spec spec_ props state action
. Union spec spec_ (ComponentSpec props state action)
=> Component props
-> { render :: Self props state action -> JSX | spec }
-> { initialState :: state, render :: Self props state action -> JSX | spec }
-> props
-> JSX

Expand All @@ -318,7 +314,7 @@ makeStateless
-> props
-> JSX
makeStateless component render =
make component { render: \self -> render self.props }
make component { initialState: unit, render: \self -> render self.props }

-- | Represents rendered React VDOM (the result of calling `React.createElement`
-- | in JavaScript).
Expand Down
64 changes: 64 additions & 0 deletions src/React/Basic/Components/Async.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
module React.Basic.Components.Async
( async
, asyncWithLoader
) where

import Prelude

import Data.Maybe (Maybe(..), fromMaybe)
import Effect.Aff (Aff, Fiber, error, killFiber, launchAff, launchAff_)
import Effect.Class (liftEffect)
import React.Basic (Component, JSX, StateUpdate(..), createComponent, empty, make, send)

component :: Component (Aff JSX)
component = createComponent "Async"

data FetchAction
= ReplaceFiber (Fiber Unit)
| UpdateJSX JSX

async :: Aff JSX -> JSX
async = asyncWithLoader empty

asyncWithLoader :: JSX -> Aff JSX -> JSX
asyncWithLoader loader = make component
{ initialState
, update
, render
, didMount: launch
-- , didUpdate: No! Implementing `didUpdate` breaks the
-- Aff/Component lifecycle relationship.
-- To update the Aff over time, wrap this
-- component with `keyed`.
, willUnmount: cleanup
}
where
initialState =
{ jsx: Nothing
, pendingFiber: pure unit
}

update { props, state } = case _ of
ReplaceFiber newFiber ->
UpdateAndSideEffects
state { jsx = Nothing, pendingFiber = newFiber }
\_ -> kill state.pendingFiber

UpdateJSX jsx ->
Update
state { jsx = Just jsx }

render self =
fromMaybe loader self.state.jsx

launch self = do
fiber <- launchAff do
jsx <- self.props
liftEffect $ send self $ UpdateJSX jsx
send self $ ReplaceFiber fiber

cleanup self =
kill self.state.pendingFiber

kill =
launchAff_ <<< killFiber (error "Cancelled")

0 comments on commit 9999161

Please sign in to comment.