Skip to content

Commit

Permalink
Merge pull request #22 from spicydonuts/aff-refactor
Browse files Browse the repository at this point in the history
Aff refactor and Suspense experiments
  • Loading branch information
megamaddu authored Apr 11, 2020
2 parents 6314a92 + 61ce4a4 commit f7addd5
Show file tree
Hide file tree
Showing 80 changed files with 2,716 additions and 862 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/bower_components/
/node_modules/
/.spago/
/.pulp-cache/
/output/
/generated-docs/
Expand Down
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"trailingComma": "none"
}
30 changes: 21 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
all: build examples
TOPTARGETS := all clean

build: bower_components node_modules
npx pulp build
EXAMPLES := $(wildcard examples/*/.)

examples: bower_components node_modules
find examples -maxdepth 2 -type f -iname makefile -execdir make \;
all: build $(EXAMPLES)

bower_components: node_modules
npx bower --allow-root install
build: output

output: .spago node_modules
npx spago build

.spago: node_modules
npx spago install

node_modules:
npm i --no-save bower pulp purescript
npm i --no-save spago purescript bower pulp

clean: $(EXAMPLES)
rm -rf node_modules bower_components .spago output

$(EXAMPLES):
cd $@ && $(MAKE) $(MAKECMDGOALS)

serve:
npx serve

.PHONY: build examples
.PHONY: build clean $(TOPTARGETS) $(EXAMPLES) serve
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ mkCounter = do

More examples:

- [Counter with an effect](./examples/counter/src/Counter.purs)
- [Reducer/action-style](./examples/reducer/src/Reducer.purs)
- [Controlled inputs](./examples/controlled-input/src/ControlledInput.purs)
- Components: [Parent](./examples/component/src/Container.purs) and [Child](./examples/component/src/ToggleButton.purs)
- [Refs to DOM nodes](./examples/refs/src/Refs.purs) (and extracting hook logic from a component for reuse)
- [A Todo App](./examples/todo-app/src/TodoApp.purs) (components, inputs, state)
- [Context](./examples/context/src/Context.purs) (creating and consuming React context)
- [Aff helper](./examples/aff/src/AffEx.purs) (async state management)
- [Counter with an effect](./examples/counter/src/Example.purs)
- [Reducer/action-style](./examples/reducer/src/Example.purs)
- [Controlled inputs](./examples/controlled-input/src/Example.purs)
- Components: [Parent](./examples/component/src/Example.purs) and [Child](./examples/component/src/ToggleButton.purs)
- [Refs to DOM nodes](./examples/refs/src/Example.purs) (and extracting hook logic from a component for reuse)
- [A Todo App](./examples/todo-app/src/Example.purs) (components, inputs, state)
- [Context](./examples/context/src/Example.purs) (creating and consuming React context)
- [Aff](./examples/aff/src/Example.purs) (rendering async data, using error boundaries)
- [Suspense](./examples/suspense/src/Example.purs) (experimental, React Suspense demo -- similar to the Aff example, but the loading state is managed by the parent instead of the detail rendering component)
11 changes: 11 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Examples

## Building an example

In the example folder:

```sh
make
```

Then open `html/index.html` in your browser.
21 changes: 16 additions & 5 deletions examples/aff/Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
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
parent_sources := $$(npx spago -x ../../spago.dhall sources | sed 's/^/..\/..\//' | tr '\n' ' ')

all: output node_modules
npx purs bundle -m Main --main Main output/*/*.js > output/bundle.js
npx browserify output/bundle.js -o html/index.js

output: node_modules
set -o noglob && \
npx purs compile \
src/**/*.purs \
$(parent_sources)

node_modules:
npm install
npm i --no-save browserify react react-dom

clean:
rm -rf node_modules output

.PHONY: all output clean
11 changes: 0 additions & 11 deletions examples/aff/README.md

This file was deleted.

9 changes: 0 additions & 9 deletions examples/aff/package.json

This file was deleted.

109 changes: 0 additions & 109 deletions examples/aff/src/AffEx.purs

This file was deleted.

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

import Prelude
import Data.Foldable (find)
import Data.Maybe (Maybe(..))
import Data.Newtype (class Newtype, un)
import Effect.Aff (Aff, Milliseconds(..), delay, error, message, throwError)
import React.Basic.DOM as R
import React.Basic.DOM.Events (capture_)
import React.Basic.Events (handler_)
import React.Basic.Hooks (Component, component, fragment, useState, (/\))
import React.Basic.Hooks as React
import React.Basic.Hooks.Aff (useAff)
import React.Basic.Hooks.ErrorBoundary (mkErrorBoundary)

mkExample :: Component Unit
mkExample = do
errorBoundary <- mkErrorBoundary "AffExErrorBoundary"
catDetails <- mkCatDetails
component "AffEx" \props -> React.do
catKey /\ setCatKey <- useState Nothing
let
reset = setCatKey \_ -> Nothing
pure
$ R.div_
[ R.h2_ [ R.text "Cat chooser" ]
, errorBoundary \{ error, dismissError } -> case error of
Just e -> renderAppError e (reset *> dismissError)
Nothing ->
fragment
[ catKeyList catKey setCatKey
, case catKey of
Nothing -> mempty
Just k -> catDetails k
]
]
where
-- This component is the main `useAff` demo. It receives a key
-- as a prop and renders both the loading state and the final
-- result.
mkCatDetails :: Component (Key Cat)
mkCatDetails = do
component "CatDetails" \catKey -> React.do
catState <- useAff catKey $ fetch catKey
pure
$ R.p_
[ case map entity catState of
Nothing -> R.text "Loading..."
Just (Cat { name }) -> R.text $ "A cat named " <> name
]

renderAppError error resetApp =
fragment
[ R.p_ [ R.text "Error!" ]
, R.p_ [ R.text $ message error ]
, R.button
{ onClick: capture_ do resetApp
, children: [ R.text "Reset" ]
}
]

catKeyList selectedCatKey setCatKey =
let
cats =
fakeDb
<> [ Entity
(Key "error (choose to throw a React render error)")
(Cat { name: "" })
]

catKeyRadioButton k =
R.div_
[ R.label_
[ R.input
{ type: "radio"
, name: "cat-key"
, checked: Just k == selectedCatKey
, onChange:
handler_ do
setCatKey \_ -> Just k
}
, R.text $ " Cat " <> un Key k
]
]
in
fragment $ map (catKeyRadioButton <<< key) cats

--
-- The bits below this point aren't directly relevant to the example,
-- just a slightly more interesting data model than returing a single
-- string.
--
--
--
-- Typed keys are a great way to tie entity-specific behavior
-- to an ID. We can use this phantom type to write a class
-- for generic, type-safe data fetching.
newtype Key entity
= Key String

derive instance eqKey :: Eq (Key entity)

derive instance ntKey :: Newtype (Key entity) _

-- An entity wrapper. In a real app this would hold other metadata
-- such as create and update dates.
data Entity entity
= Entity (Key entity) entity

key :: forall entity. Entity entity -> Key entity
key (Entity k _) = k

entity :: forall entity. Entity entity -> entity
entity (Entity _ e) = e

class Fetch entity where
fetch :: Key entity -> Aff (Entity entity)

-- An example entity
newtype Cat
= Cat { name :: String }

fakeDb :: Array (Entity Cat)
fakeDb =
[ Entity (Key "abc") (Cat { name: "Herb" })
, Entity (Key "def") (Cat { name: "Maxi" })
, Entity (Key "ghi") (Cat { name: "Chloe" })
]

instance fetchCat :: Fetch Cat where
fetch k = do
delay $ Milliseconds 300.0
-- pretend this happens on the server
case fakeDb # find (key >>> (_ == k)) of
Nothing ->
-- This should never happen in a normal application path
-- if only the server can generate keys :)
throwError
$ error
$ "DB error: Cat not found for key "
<> un Key k
Just e -> pure e
Loading

0 comments on commit f7addd5

Please sign in to comment.