Skip to content

Commit 3f3cf28

Browse files
committed
Expand Aff example, adjust Tuple exports
1 parent 9d64a57 commit 3f3cf28

File tree

7 files changed

+113
-50
lines changed

7 files changed

+113
-50
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
/.psc*
88
/.purs*
99
/.psa*
10+
/.vscode/

examples/aff/src/AffEx.purs

+87-31
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,103 @@ module AffEx where
22

33
import Prelude
44

5-
import Data.Either (Either(..))
6-
import Data.Maybe (Maybe(..))
5+
import Data.Either (either)
6+
import Data.Maybe (Maybe(..), maybe)
77
import Effect.Aff (Aff, Milliseconds(..), delay, error, message, throwError)
88
import React.Basic.DOM as R
99
import React.Basic.Events (handler_)
10-
import React.Basic.Hooks (CreateComponent, component, fragment, (/\))
10+
import React.Basic.Hooks (type (/\), CreateComponent, Hook, JSX, component, element, fragment, useState, (/\))
1111
import React.Basic.Hooks as React
1212
import React.Basic.Hooks.Aff (useAff)
13-
import React.Basic.Hooks.ResetToken (useResetToken)
1413

1514
mkAffEx :: CreateComponent {}
1615
mkAffEx = do
16+
-- A component for fetching and rendering a Cat entity.
17+
catDetails <- mkCatDetails
18+
1719
component "AffEx" \props -> React.do
18-
let id = 0 -- pretend this is a prop
19-
resetToken /\ reset <- useResetToken
20-
r1 <- useAff (id /\ resetToken) delayedSuccess
21-
r2 <- useAff (id /\ resetToken) delayedFailure
22-
23-
pure $ fragment
24-
[ R.button
25-
{ onClick: handler_ reset
26-
, children: [ R.text "Reset" ]
27-
}
28-
, showResult 1 r1
29-
, showResult 2 r2
30-
]
20+
catKey /\ catChooser <- useCatKeyChooser
21+
22+
pure $ 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."
31+
]
32+
, catChooser
33+
, R.p_
34+
[ case catKey of
35+
Nothing -> mempty
36+
Just k -> element catDetails { catKey: k }
37+
]
38+
]
39+
}
3140
where
32-
showResult n r =
33-
R.div_
34-
[ R.text $ "Request " <> show n <> ": " <> case r of
35-
Nothing -> "loading..."
36-
Just (Left err) -> message err
37-
Just (Right msg) -> msg
41+
-- This hook packages up some interactive UI and the current
42+
-- selection the user has made via that UI.
43+
useCatKeyChooser :: Hook _ ((Maybe (Key Cat)) /\ JSX)
44+
useCatKeyChooser = React.do
45+
catKey /\ setCatKey <- useState Nothing
46+
let
47+
catChoice key =
48+
R.label_
49+
[ R.input
50+
{ type: "radio"
51+
, name: "cat-key"
52+
, checked: Just key == catKey
53+
, onChange: handler_ do
54+
setCatKey \_ -> Just key
55+
}
56+
, R.text $ showCatKey key
57+
]
58+
59+
showCatKey :: Key Cat -> String
60+
showCatKey (Key key) = "Cat " <> key
61+
62+
pure $ catKey /\ fragment
63+
[ catChoice $ Key "abc"
64+
, catChoice $ Key "def"
65+
, catChoice $ Key "xyz"
3866
]
3967

40-
delayedSuccess :: Aff String
41-
delayedSuccess = do
42-
delay $ Milliseconds 1000.0
43-
pure "Success!"
68+
-- Hooks can't be used conditionally but components can!
69+
-- Not needing to deal with a `Maybe` key simplifies this
70+
-- compoennt a bit.
71+
mkCatDetails :: CreateComponent { catKey :: Key Cat }
72+
mkCatDetails = do
73+
component "CatDetails" \{ catKey } -> React.do
74+
cat <- useAff catKey $ fetch catKey
75+
pure $ R.text $
76+
maybe "Loading..." (either message showCat) cat
77+
where
78+
showCat (Cat { name }) = "A cat named " <> name
79+
80+
81+
-- Typed keys are a great way to tie entity-specific behavior
82+
-- to an ID. We can use this phantom type to write a class
83+
-- for generic, type-safe data fetching.
84+
newtype Key entity = Key String
85+
derive instance eqKey :: Eq (Key entity)
86+
87+
class Fetch entity where
88+
fetch :: Key entity -> Aff entity
89+
90+
91+
-- An example entity
92+
newtype Cat = Cat { name :: String }
4493

45-
delayedFailure :: Aff String
46-
delayedFailure = do
47-
delay $ Milliseconds 2000.0
48-
throwError $ error "Failure!"
94+
instance fetchCat :: Fetch Cat where
95+
fetch = case _ of
96+
Key "abc" -> do
97+
delay $ Milliseconds 300.0
98+
pure $ Cat { name: "Herb" }
99+
Key "def" -> do
100+
delay $ Milliseconds 600.0
101+
pure $ Cat { name: "Maxi" }
102+
_ -> do
103+
delay $ Milliseconds 900.0
104+
throwError $ error "Cat not found (intended example behavior 😅)"

examples/context/src/Context.purs

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Data.Maybe (Maybe(..))
66
import Effect (Effect)
77
import React.Basic.DOM as R
88
import React.Basic.Events (handler_)
9-
import React.Basic.Hooks (Context, CreateComponent, JSX, Tuple, component, contextProvider, createContext, element, fragment, useContext, useState, (/\))
9+
import React.Basic.Hooks (Context, CreateComponent, JSX, type (/\), component, contextProvider, createContext, element, fragment, useContext, useState, (/\))
1010
import React.Basic.Hooks as React
1111

1212
mkContext :: CreateComponent {}
@@ -27,7 +27,7 @@ mkContext = do
2727
}
2828

2929
mkStore
30-
:: Context (Tuple Int (Effect Unit))
30+
:: Context (Int /\ (Effect Unit))
3131
-> CreateComponent { children :: Array JSX }
3232
mkStore context = do
3333
component "Store" \{ children } -> React.do
@@ -39,7 +39,7 @@ mkStore context = do
3939
(fragment children)
4040

4141
mkCounter
42-
:: Context (Tuple Int (Effect Unit))
42+
:: Context (Int /\ (Effect Unit))
4343
-> CreateComponent {}
4444
mkCounter counterContext = do
4545
component "Counter" \props -> React.do

examples/refs/src/Refs.purs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Data.Maybe (Maybe(..))
77
import Data.Nullable (Nullable, null)
88
import Math (pow, sqrt)
99
import React.Basic.DOM as R
10-
import React.Basic.Hooks (CreateComponent, Ref, Tuple, UseEffect, UseRef, UseState, Hook, component, element, fragment, readRefMaybe, useEffect, useRef, useState, (/\))
10+
import React.Basic.Hooks (CreateComponent, Ref, UseEffect, UseRef, UseState, Hook, type (/\), component, element, fragment, readRefMaybe, useEffect, useRef, useState, (/\))
1111
import React.Basic.Hooks as React
1212
import Unsafe.Coerce (unsafeCoerce)
1313
import Web.DOM (Node)
@@ -46,7 +46,7 @@ mkRefs = do
4646

4747
type UseNodeDistance hooks = UseEffect Unit (UseState Int (UseRef (Nullable Node) hooks))
4848

49-
useNodeDistanceFromMouse :: Hook UseNodeDistance (Tuple Int (Ref (Nullable Node)))
49+
useNodeDistanceFromMouse :: Hook UseNodeDistance (Int /\ (Ref (Nullable Node)))
5050
useNodeDistanceFromMouse = React.do
5151
elementRef <- useRef null
5252
mouseDistance /\ setMouseDistance <- useState 0

src/React/Basic/Hooks.purs

+12-10
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ module React.Basic.Hooks
5050
, discard
5151
, displayName
5252
, module React.Basic
53-
, module Data.Tuple
5453
, module Data.Tuple.Nested
5554
) where
5655

57-
import Prelude hiding (bind, discard)
56+
import Prelude hiding (bind,discard)
5857

5958
import Control.Applicative.Indexed (class IxApplicative)
6059
import Control.Apply.Indexed (class IxApply)
@@ -65,10 +64,11 @@ import Data.Maybe (Maybe)
6564
import Data.Newtype (class Newtype)
6665
import Data.Nullable (Nullable, toMaybe)
6766
import Data.Tuple (Tuple(..))
68-
import Data.Tuple.Nested (tuple2, (/\))
67+
import Data.Tuple.Nested (type (/\), (/\))
6968
import Effect (Effect)
7069
import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, mkEffectFn1, runEffectFn1, runEffectFn2, runEffectFn3)
7170
import Prelude (bind) as Prelude
71+
import Prim.Row (class Lacks)
7272
import React.Basic (JSX, ReactComponent, empty, keyed, fragment, element, elementKeyed)
7373
import Type.Equality (class TypeEquals)
7474
import Unsafe.Coerce (unsafeCoerce)
@@ -84,7 +84,9 @@ type CreateComponent props = Effect (ReactComponent props)
8484
-- | lifecycles or render functions.
8585
component
8686
:: forall hooks props
87-
. String
87+
. Lacks "key" props
88+
=> Lacks "ref" props
89+
=> String
8890
-> ({ | props } -> Render Unit hooks JSX)
8991
-> CreateComponent { | props }
9092
component name renderFn =
@@ -105,7 +107,7 @@ foreign import data UseState :: Type -> Type -> Type
105107
useState
106108
:: forall state
107109
. state
108-
-> Hook (UseState state) (Tuple state ((state -> state) -> Effect Unit))
110+
-> Hook (UseState state) (state /\ ((state -> state) -> Effect Unit))
109111
useState initialState = Render do
110112
runEffectFn2 useState_ (mkFn2 Tuple) initialState
111113

@@ -135,7 +137,7 @@ useReducer
135137
:: forall state action
136138
. state
137139
-> (state -> action -> state)
138-
-> Hook (UseReducer state action) (Tuple state (action -> Effect Unit))
140+
-> Hook (UseReducer state action) (state /\ (action -> Effect Unit))
139141
useReducer initialState reducer = Render do
140142
runEffectFn3 useReducer_
141143
(mkFn2 Tuple)
@@ -286,9 +288,9 @@ foreign import unsafeSetDisplayName
286288
foreign import useState_
287289
:: forall state
288290
. EffectFn2
289-
(forall a b. Fn2 a b (Tuple a b))
291+
(forall a b. Fn2 a b (a /\ b))
290292
state
291-
(Tuple state ((state -> state) -> Effect Unit))
293+
(state /\ ((state -> state) -> Effect Unit))
292294

293295
foreign import useEffect_
294296
:: forall key
@@ -309,10 +311,10 @@ foreign import useLayoutEffect_
309311
foreign import useReducer_
310312
:: forall state action
311313
. EffectFn3
312-
(forall a b. Fn2 a b (Tuple a b))
314+
(forall a b. Fn2 a b (a /\ b))
313315
(Fn2 state action state)
314316
state
315-
(Tuple state (action -> Effect Unit))
317+
(state /\ (action -> Effect Unit))
316318

317319
foreign import readRef_
318320
:: forall a

src/React/Basic/Hooks/Aff.purs

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ import React.Basic.Hooks (Hook, UseEffect, UseState, useEffect, useState, (/\))
1010
import React.Basic.Hooks as React
1111

1212
type UseAff key a hooks =
13-
UseEffect key (UseState (Maybe (Either Error a)) hooks)
13+
UseEffect key (UseState (Result a) hooks)
14+
15+
type Result a = Maybe (Either Error a)
1416

1517
useAff
1618
:: forall key a
1719
. Eq key
1820
=> key
1921
-> Aff a
20-
-> Hook (UseAff key a) (Maybe (Either Error a))
22+
-> Hook (UseAff key a) (Result a)
2123
useAff key aff = React.do
2224
result /\ setResult <- useState Nothing
2325
useEffect key do

src/React/Basic/Hooks/ResetToken.purs

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import Prelude
88

99
import Effect (Effect)
1010
import Effect.Unsafe (unsafePerformEffect)
11-
import React.Basic.Hooks (Hook, Tuple, UseState, useState, (/\))
11+
import React.Basic.Hooks (Hook, UseState, type (/\), useState, (/\))
1212
import React.Basic.Hooks as React
1313
import Unsafe.Coerce (unsafeCoerce)
1414
import Unsafe.Reference (unsafeRefEq)
1515

1616
type UseResetToken = UseState ResetToken
1717

18-
useResetToken :: Hook UseResetToken (Tuple ResetToken (Effect Unit))
18+
-- Useful for resetting effects or component state in place of a
19+
-- real key, i.e. a "reset" or "force rerender" button.
20+
useResetToken :: Hook UseResetToken (ResetToken /\ (Effect Unit))
1921
useResetToken = React.do
2022
resetToken /\ setResetToken <- useState initialResetToken
2123
let

0 commit comments

Comments
 (0)