Skip to content

Commit f7addd5

Browse files
authored
Merge pull request #22 from spicydonuts/aff-refactor
Aff refactor and Suspense experiments
2 parents 6314a92 + 61ce4a4 commit f7addd5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+2716
-862
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/bower_components/
22
/node_modules/
3+
/.spago/
34
/.pulp-cache/
45
/output/
56
/generated-docs/

Diff for: .prettierrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"trailingComma": "none"
3+
}

Diff for: Makefile

+21-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1-
all: build examples
1+
TOPTARGETS := all clean
22

3-
build: bower_components node_modules
4-
npx pulp build
3+
EXAMPLES := $(wildcard examples/*/.)
54

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

9-
bower_components: node_modules
10-
npx bower --allow-root install
7+
build: output
8+
9+
output: .spago node_modules
10+
npx spago build
11+
12+
.spago: node_modules
13+
npx spago install
1114

1215
node_modules:
13-
npm i --no-save bower pulp purescript
16+
npm i --no-save spago purescript bower pulp
17+
18+
clean: $(EXAMPLES)
19+
rm -rf node_modules bower_components .spago output
20+
21+
$(EXAMPLES):
22+
cd $@ && $(MAKE) $(MAKECMDGOALS)
23+
24+
serve:
25+
npx serve
1426

15-
.PHONY: build examples
27+
.PHONY: build clean $(TOPTARGETS) $(EXAMPLES) serve

Diff for: README.md

+9-8
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ mkCounter = do
3131

3232
More examples:
3333

34-
- [Counter with an effect](./examples/counter/src/Counter.purs)
35-
- [Reducer/action-style](./examples/reducer/src/Reducer.purs)
36-
- [Controlled inputs](./examples/controlled-input/src/ControlledInput.purs)
37-
- Components: [Parent](./examples/component/src/Container.purs) and [Child](./examples/component/src/ToggleButton.purs)
38-
- [Refs to DOM nodes](./examples/refs/src/Refs.purs) (and extracting hook logic from a component for reuse)
39-
- [A Todo App](./examples/todo-app/src/TodoApp.purs) (components, inputs, state)
40-
- [Context](./examples/context/src/Context.purs) (creating and consuming React context)
41-
- [Aff helper](./examples/aff/src/AffEx.purs) (async state management)
34+
- [Counter with an effect](./examples/counter/src/Example.purs)
35+
- [Reducer/action-style](./examples/reducer/src/Example.purs)
36+
- [Controlled inputs](./examples/controlled-input/src/Example.purs)
37+
- Components: [Parent](./examples/component/src/Example.purs) and [Child](./examples/component/src/ToggleButton.purs)
38+
- [Refs to DOM nodes](./examples/refs/src/Example.purs) (and extracting hook logic from a component for reuse)
39+
- [A Todo App](./examples/todo-app/src/Example.purs) (components, inputs, state)
40+
- [Context](./examples/context/src/Example.purs) (creating and consuming React context)
41+
- [Aff](./examples/aff/src/Example.purs) (rendering async data, using error boundaries)
42+
- [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)

Diff for: examples/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Examples
2+
3+
## Building an example
4+
5+
In the example folder:
6+
7+
```sh
8+
make
9+
```
10+
11+
Then open `html/index.html` in your browser.

Diff for: examples/aff/Makefile

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
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
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)
512

613
node_modules:
7-
npm install
14+
npm i --no-save browserify react react-dom
15+
16+
clean:
17+
rm -rf node_modules output
818

19+
.PHONY: all output clean

Diff for: examples/aff/README.md

-11
This file was deleted.

Diff for: examples/aff/package.json

-9
This file was deleted.

Diff for: examples/aff/src/AffEx.purs

-109
This file was deleted.

Diff for: examples/aff/src/Example.purs

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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 Effect.Aff (Aff, Milliseconds(..), delay, error, message, throwError)
8+
import React.Basic.DOM as R
9+
import React.Basic.DOM.Events (capture_)
10+
import React.Basic.Events (handler_)
11+
import React.Basic.Hooks (Component, component, fragment, useState, (/\))
12+
import React.Basic.Hooks as React
13+
import React.Basic.Hooks.Aff (useAff)
14+
import React.Basic.Hooks.ErrorBoundary (mkErrorBoundary)
15+
16+
mkExample :: Component Unit
17+
mkExample = do
18+
errorBoundary <- mkErrorBoundary "AffExErrorBoundary"
19+
catDetails <- mkCatDetails
20+
component "AffEx" \props -> React.do
21+
catKey /\ setCatKey <- useState Nothing
22+
let
23+
reset = setCatKey \_ -> Nothing
24+
pure
25+
$ R.div_
26+
[ R.h2_ [ R.text "Cat chooser" ]
27+
, errorBoundary \{ error, dismissError } -> case error of
28+
Just e -> renderAppError e (reset *> dismissError)
29+
Nothing ->
30+
fragment
31+
[ catKeyList catKey setCatKey
32+
, case catKey of
33+
Nothing -> mempty
34+
Just k -> catDetails k
35+
]
36+
]
37+
where
38+
-- This component is the main `useAff` demo. It receives a key
39+
-- as a prop and renders both the loading state and the final
40+
-- result.
41+
mkCatDetails :: Component (Key Cat)
42+
mkCatDetails = do
43+
component "CatDetails" \catKey -> React.do
44+
catState <- useAff catKey $ fetch catKey
45+
pure
46+
$ R.p_
47+
[ case map entity catState of
48+
Nothing -> R.text "Loading..."
49+
Just (Cat { name }) -> R.text $ "A cat named " <> name
50+
]
51+
52+
renderAppError error resetApp =
53+
fragment
54+
[ R.p_ [ R.text "Error!" ]
55+
, R.p_ [ R.text $ message error ]
56+
, R.button
57+
{ onClick: capture_ do resetApp
58+
, children: [ R.text "Reset" ]
59+
}
60+
]
61+
62+
catKeyList selectedCatKey setCatKey =
63+
let
64+
cats =
65+
fakeDb
66+
<> [ Entity
67+
(Key "error (choose to throw a React render error)")
68+
(Cat { name: "" })
69+
]
70+
71+
catKeyRadioButton k =
72+
R.div_
73+
[ R.label_
74+
[ R.input
75+
{ type: "radio"
76+
, name: "cat-key"
77+
, checked: Just k == selectedCatKey
78+
, onChange:
79+
handler_ do
80+
setCatKey \_ -> Just k
81+
}
82+
, R.text $ " Cat " <> un Key k
83+
]
84+
]
85+
in
86+
fragment $ map (catKeyRadioButton <<< key) cats
87+
88+
--
89+
-- The bits below this point aren't directly relevant to the example,
90+
-- just a slightly more interesting data model than returing a single
91+
-- string.
92+
--
93+
--
94+
--
95+
-- Typed keys are a great way to tie entity-specific behavior
96+
-- to an ID. We can use this phantom type to write a class
97+
-- for generic, type-safe data fetching.
98+
newtype Key entity
99+
= Key String
100+
101+
derive instance eqKey :: Eq (Key entity)
102+
103+
derive instance ntKey :: Newtype (Key entity) _
104+
105+
-- An entity wrapper. In a real app this would hold other metadata
106+
-- such as create and update dates.
107+
data Entity entity
108+
= Entity (Key entity) entity
109+
110+
key :: forall entity. Entity entity -> Key entity
111+
key (Entity k _) = k
112+
113+
entity :: forall entity. Entity entity -> entity
114+
entity (Entity _ e) = e
115+
116+
class Fetch entity where
117+
fetch :: Key entity -> Aff (Entity entity)
118+
119+
-- An example entity
120+
newtype Cat
121+
= Cat { name :: String }
122+
123+
fakeDb :: Array (Entity Cat)
124+
fakeDb =
125+
[ Entity (Key "abc") (Cat { name: "Herb" })
126+
, Entity (Key "def") (Cat { name: "Maxi" })
127+
, Entity (Key "ghi") (Cat { name: "Chloe" })
128+
]
129+
130+
instance fetchCat :: Fetch Cat where
131+
fetch k = do
132+
delay $ Milliseconds 300.0
133+
-- pretend this happens on the server
134+
case fakeDb # find (key >>> (_ == k)) of
135+
Nothing ->
136+
-- This should never happen in a normal application path
137+
-- if only the server can generate keys :)
138+
throwError
139+
$ error
140+
$ "DB error: Cat not found for key "
141+
<> un Key k
142+
Just e -> pure e

0 commit comments

Comments
 (0)