diff --git a/README.md b/README.md
index b8d6b9e0..185c44c0 100644
--- a/README.md
+++ b/README.md
@@ -104,7 +104,10 @@ Running a web-compatible recipe:
| | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/ClockReactHooks/src/Main.purs)) | [ClockReactHooks](recipes/ClockReactHooks) | A React port of the ["User Interface - Clock" Elm Example](https://elm-lang.org/examples/clock). |
| | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/ComponentsHalogenHooks/src/Main.purs)) | [ComponentsHalogenHooks](recipes/ComponentsHalogenHooks) | Demonstrates how to nest one Halogen-Hooks-based component inside another and send/receive queries between the two. |
| | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/ComponentsInputHalogenHooks/src/Main.purs)) | [ComponentsInputHalogenHooks](recipes/ComponentsInputHalogenHooks) | Each time a parent re-renders, it will pass a new input value into the child, and the child will update accordingly. |
+| | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/ComponentsInputReactHooks/src/Main.purs)) | [ComponentsInputReactHooks](recipes/ComponentsInputReactHooks) | Each time the parent's state updates, it will pass a new prop value into the child, and the child will update accordingly. |
| | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/ComponentsMultiTypeHalogenHooks/src/Main.purs)) | [ComponentsMultiTypeHalogenHooks](recipes/ComponentsMultiTypeHalogenHooks) | Demonstrates a component that can communicate with its children that have differing types. |
+| | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/ComponentsMultiTypeReactHooks/src/Main.purs)) | [ComponentsMultiTypeReactHooks](recipes/ComponentsMultiTypeReactHooks) | Demonstrates a parent component with several children components, each with different prop types. |
+| | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/ComponentsReactHooks/src/Main.purs)) | [ComponentsReactHooks](recipes/ComponentsReactHooks) | Demonstrates how to nest one React Hooks-based component inside another and send props from the parent to the child component. |
| :heavy_check_mark: | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/DateTimeBasicsLog/src/Main.purs)) | [DateTimeBasicsLog](recipes/DateTimeBasicsLog) | This recipe shows how to use `purescript-datetime` library to create `Time`, `Date`, and `DateTime` values and adjust/diff them. |
| :heavy_check_mark: | :heavy_check_mark: ([try](https://try.ps.ai/?github=JordanMartinez/purescript-cookbook/master/recipes/DebuggingLog/src/Main.purs)) | [DebuggingLog](recipes/DebuggingLog) | This recipe shows how to do print-debugging using the `Debug` module's `spy` and `traceM` functions. The compiler will emit warnings to remind you to remove these debug functions before you ship production code. |
| :heavy_check_mark: | | [DiceCLI](recipes/DiceCLI) | This recipe shows how to create an interactive command line prompt that repeatedly generates a random number between 1 and 6. |
diff --git a/recipes/ComponentsHalogenHooks/README.md b/recipes/ComponentsHalogenHooks/README.md
index 5cf2a86c..6111d450 100644
--- a/recipes/ComponentsHalogenHooks/README.md
+++ b/recipes/ComponentsHalogenHooks/README.md
@@ -7,3 +7,4 @@ Demonstrates how to nest one Halogen-Hooks-based component inside another and se
### Browser
Clicking the "On/Off" button in the child will toggle the button's label and update the parent component's toggle count. If the user clicks the "Check now" button in the parent, the parent will update its cache of the child's label state.
+The parent will also display how many times it has been rendered.
diff --git a/recipes/ComponentsHalogenHooks/src/Main.purs b/recipes/ComponentsHalogenHooks/src/Main.purs
index 0142032c..ac1cd0fd 100644
--- a/recipes/ComponentsHalogenHooks/src/Main.purs
+++ b/recipes/ComponentsHalogenHooks/src/Main.purs
@@ -6,11 +6,15 @@ import Data.Maybe (Maybe(..), maybe)
import Data.Symbol (SProxy(..))
import Data.Tuple.Nested ((/\))
import Effect (Effect)
+import Effect.Class (class MonadEffect)
+import Effect.Ref as Ref
+import Halogen (liftEffect)
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.HTML.Properties as HP
+import Halogen.Hooks (Hooked, UseEffect, UseRef)
import Halogen.Hooks as Hooks
import Halogen.VDom.Driver (runUI)
@@ -25,8 +29,10 @@ _button = SProxy
containerComponent
:: forall unusedQuery unusedInput unusedOutput anyMonad
- . H.Component HH.HTML unusedQuery unusedInput unusedOutput anyMonad
+ . MonadEffect anyMonad
+ => H.Component HH.HTML unusedQuery unusedInput unusedOutput anyMonad
containerComponent = Hooks.component \rec _ -> Hooks.do
+ parentRenders <- useRenderCount
toggleCount /\ toggleCountIdx <- Hooks.useState 0
buttonState /\ buttonStateIdx <- Hooks.useState (Nothing :: Maybe Boolean)
Hooks.pure $
@@ -47,6 +53,8 @@ containerComponent = Hooks.component \rec _ -> Hooks.do
]
[ HH.text "Check now" ]
]
+ , HH.p_
+ [ HH.text ("Parent has been rendered " <> show parentRenders <> " time(s)") ]
]
data ButtonMessage = Toggled Boolean
@@ -70,3 +78,14 @@ buttonComponent = Hooks.component \rec _ -> Hooks.do
Hooks.raise rec.outputToken $ Toggled newState
]
[ HH.text label ]
+
+useRenderCount
+ :: forall m a
+ . MonadEffect m
+ => Hooked m a (UseEffect (UseRef Int a)) Int
+useRenderCount = Hooks.do
+ renders /\ rendersRef <- Hooks.useRef 1
+ Hooks.captures {} Hooks.useTickEffect do
+ liftEffect (Ref.modify_ (_ + 1) rendersRef)
+ mempty
+ Hooks.pure renders
\ No newline at end of file
diff --git a/recipes/ComponentsInputHalogenHooks/README.md b/recipes/ComponentsInputHalogenHooks/README.md
index 7116c6a3..a1cb1639 100644
--- a/recipes/ComponentsInputHalogenHooks/README.md
+++ b/recipes/ComponentsInputHalogenHooks/README.md
@@ -7,3 +7,4 @@ Each time a parent re-renders, it will pass a new input value into the child, an
### Browser
The parent stores an `Int`. Clicking the buttons will increment/decrement that value. The children will produce the result of a mathematical computation using that value.
+The parent will also display how many times it has been rendered.
diff --git a/recipes/ComponentsInputHalogenHooks/src/Main.purs b/recipes/ComponentsInputHalogenHooks/src/Main.purs
index 078dec92..3ed22a7f 100644
--- a/recipes/ComponentsInputHalogenHooks/src/Main.purs
+++ b/recipes/ComponentsInputHalogenHooks/src/Main.purs
@@ -6,11 +6,13 @@ import Data.Maybe (Maybe(..))
import Data.Symbol (SProxy(..))
import Data.Tuple.Nested ((/\))
import Effect (Effect)
-import Effect.Class (class MonadEffect)
+import Effect.Class (class MonadEffect, liftEffect)
+import Effect.Ref as Ref
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
+import Halogen.Hooks (Hooked, UseEffect, UseRef)
import Halogen.Hooks as Hooks
import Halogen.VDom.Driver (runUI)
@@ -28,6 +30,7 @@ containerComponent
. MonadEffect anyMonad
=> H.Component HH.HTML unusedQuery unusedInput unusedOutput anyMonad
containerComponent = Hooks.component \_ _ -> Hooks.do
+ parentRenders <- useRenderCount
state /\ stateIdx <- Hooks.useState 0
Hooks.pure $
HH.div_
@@ -44,6 +47,8 @@ containerComponent = Hooks.component \_ _ -> Hooks.do
, HH.button
[ HE.onClick \_ -> Just $ Hooks.modify_ stateIdx (_ - 1) ]
[ HH.text "-1"]
+ , HH.p_
+ [ HH.text ("Parent has been rendered " <> show parentRenders <> " time(s)") ]
]
_display = SProxy :: SProxy "display"
@@ -58,3 +63,14 @@ displayComponent = Hooks.component \_ input -> Hooks.do
[ HH.text "My input value is:"
, HH.strong_ [ HH.text (show input) ]
]
+
+useRenderCount
+ :: forall m a
+ . MonadEffect m
+ => Hooked m a (UseEffect (UseRef Int a)) Int
+useRenderCount = Hooks.do
+ renders /\ rendersRef <- Hooks.useRef 1
+ Hooks.captures {} Hooks.useTickEffect do
+ liftEffect (Ref.modify_ (_ + 1) rendersRef)
+ mempty
+ Hooks.pure renders
\ No newline at end of file
diff --git a/recipes/ComponentsInputReactHooks/.gitignore b/recipes/ComponentsInputReactHooks/.gitignore
new file mode 100644
index 00000000..57030e7b
--- /dev/null
+++ b/recipes/ComponentsInputReactHooks/.gitignore
@@ -0,0 +1,15 @@
+/bower_components/
+/node_modules/
+/.pulp-cache/
+/output/
+/generated-docs/
+/.psc-package/
+/.psc*
+/.purs*
+/.psa*
+/.spago
+/.cache/
+/dist/
+/web-dist/
+/prod-dist/
+/prod/
diff --git a/recipes/ComponentsInputReactHooks/README.md b/recipes/ComponentsInputReactHooks/README.md
new file mode 100644
index 00000000..7e145003
--- /dev/null
+++ b/recipes/ComponentsInputReactHooks/README.md
@@ -0,0 +1,15 @@
+# ComponentsInputReactHooks
+
+Each time the parent's state updates, it will pass a new prop value into the child, and the child will update accordingly.
+
+## Expected Behavior:
+
+### Browser
+
+The parent stores an `Int` as state. Clicking the buttons will increment/decrement that value. The children will display the result of a mathematical computation using that value.
+The parent will also display how many times it has been rendered.
+
+## Dependencies Used:
+
+[react](https://www.npmjs.com/package/react)
+[react-dom](https://www.npmjs.com/package/react-dom)
diff --git a/recipes/ComponentsInputReactHooks/spago.dhall b/recipes/ComponentsInputReactHooks/spago.dhall
new file mode 100644
index 00000000..8be6669d
--- /dev/null
+++ b/recipes/ComponentsInputReactHooks/spago.dhall
@@ -0,0 +1,11 @@
+{ name = "ComponentsInputReactHooks"
+, dependencies =
+ [ "console"
+ , "effect"
+ , "psci-support"
+ , "react-basic-hooks"
+ , "react-basic-dom"
+ ]
+, packages = ../../packages.dhall
+, sources = [ "recipes/ComponentsInputReactHooks/src/**/*.purs" ]
+}
diff --git a/recipes/ComponentsInputReactHooks/src/Main.purs b/recipes/ComponentsInputReactHooks/src/Main.purs
new file mode 100644
index 00000000..b6f8e139
--- /dev/null
+++ b/recipes/ComponentsInputReactHooks/src/Main.purs
@@ -0,0 +1,84 @@
+module ComponentsInputReactHooks.Main where
+
+import Prelude
+import Data.Maybe (Maybe(..))
+import Effect (Effect)
+import Effect.Exception (throw)
+import Effect.Unsafe (unsafePerformEffect)
+import React.Basic.DOM (render)
+import React.Basic.DOM as R
+import React.Basic.Events (handler_)
+import React.Basic.Hooks
+ ( Component
+ , Render
+ , UseEffect
+ , UseRef
+ , component
+ , readRef
+ , useEffectAlways
+ , useRef
+ , useState
+ , writeRef
+ , (/\)
+ )
+import React.Basic.Hooks as React
+import Web.HTML (window)
+import Web.HTML.HTMLDocument (body)
+import Web.HTML.HTMLElement (toElement)
+import Web.HTML.Window (document)
+
+main :: Effect Unit
+main = do
+ body <- body =<< document =<< window
+ case body of
+ Nothing -> throw "Could not find body."
+ Just b -> do
+ container <- mkContainer
+ render (container unit) (toElement b)
+
+mkContainer :: Component Unit
+mkContainer = do
+ display <- mkDisplay
+ component "Container" \_ -> React.do
+ parentRenders <- useRenderCount
+ state /\ setState <- useState 0
+ pure
+ $ R.div_
+ [ R.ul_
+ [ display state
+ , display (state * 2)
+ , display (state * 3)
+ , display (state * 10)
+ , display (state * state)
+ ]
+ , R.button
+ { onClick: handler_ (setState (_ + 1))
+ , children: [ R.text "+1" ]
+ }
+ , R.button
+ { onClick: handler_ (setState (_ - 1))
+ , children: [ R.text "-1" ]
+ }
+ , R.p_
+ [ R.text ("Parent has been rendered " <> show parentRenders <> " time(s)") ]
+ ]
+
+mkDisplay :: Component Int
+mkDisplay =
+ component "Display" \n ->
+ pure
+ $ R.div_
+ [ R.text "My input value is: "
+ , R.strong_ [ R.text (show n) ]
+ ]
+
+useRenderCount :: forall a. Render a (UseEffect Unit (UseRef Int a)) Int
+useRenderCount = React.do
+ rendersRef <- useRef 1
+ useEffectAlways do
+ renders <- readRef rendersRef
+ writeRef rendersRef (renders + 1)
+ pure mempty
+ let
+ renders = unsafePerformEffect (readRef rendersRef)
+ pure renders
diff --git a/recipes/ComponentsInputReactHooks/web/index.html b/recipes/ComponentsInputReactHooks/web/index.html
new file mode 100644
index 00000000..bfbb931a
--- /dev/null
+++ b/recipes/ComponentsInputReactHooks/web/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ ComponentsInputReactHooks
+
+
+
+
+
+
+
diff --git a/recipes/ComponentsInputReactHooks/web/index.js b/recipes/ComponentsInputReactHooks/web/index.js
new file mode 100644
index 00000000..3d77948a
--- /dev/null
+++ b/recipes/ComponentsInputReactHooks/web/index.js
@@ -0,0 +1,2 @@
+"use strict";
+require("../../../output/ComponentsInputReactHooks.Main/index.js").main();
diff --git a/recipes/ComponentsMultiTypeHalogenHooks/README.md b/recipes/ComponentsMultiTypeHalogenHooks/README.md
index 7b0cf08c..e64abafe 100644
--- a/recipes/ComponentsMultiTypeHalogenHooks/README.md
+++ b/recipes/ComponentsMultiTypeHalogenHooks/README.md
@@ -7,3 +7,4 @@ Demonstrates a component that can communicate with its children that have differ
### Browser
A toggle button, a count button, and an input field are the children of the parent. The user can modify either of those children's values and then click the "Check now" button. The parent will get the latest state from each of its children and display all states.
+The parent will also display how many times it has been rendered.
diff --git a/recipes/ComponentsMultiTypeHalogenHooks/src/Main.purs b/recipes/ComponentsMultiTypeHalogenHooks/src/Main.purs
index 635eb6fa..60508b63 100644
--- a/recipes/ComponentsMultiTypeHalogenHooks/src/Main.purs
+++ b/recipes/ComponentsMultiTypeHalogenHooks/src/Main.purs
@@ -6,11 +6,14 @@ import Data.Maybe (Maybe(..))
import Data.Symbol (SProxy(..))
import Data.Tuple.Nested ((/\))
import Effect (Effect)
+import Effect.Class (class MonadEffect, liftEffect)
+import Effect.Ref as Ref
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.HTML.Properties as HP
+import Halogen.Hooks (Hooked, UseEffect, UseRef)
import Halogen.Hooks as Hooks
import Halogen.VDom.Driver (runUI)
@@ -25,8 +28,10 @@ _button = SProxy
containerComponent
:: forall unusedQuery unusedInput unusedOutput anyMonad
- . H.Component HH.HTML unusedQuery unusedInput unusedOutput anyMonad
+ . MonadEffect anyMonad
+ => H.Component HH.HTML unusedQuery unusedInput unusedOutput anyMonad
containerComponent = Hooks.component \rec _ -> Hooks.do
+ parentRenders <- useRenderCount
state /\ stateIdx <- Hooks.useState { a: Nothing, b: Nothing, c: Nothing }
let
_a = SProxy :: SProxy "a"
@@ -65,6 +70,8 @@ containerComponent = Hooks.component \rec _ -> Hooks.do
Hooks.put stateIdx { a, b, c }
]
[ HH.text "Check states now" ]
+ , HH.p_
+ [ HH.text ("Parent has been rendered " <> show parentRenders <> " time(s)") ]
]
data QueryA a = IsOn (Boolean -> a)
@@ -128,3 +135,14 @@ componentC = Hooks.component \rec _ -> Hooks.do
, HE.onValueInput (Just <<< Hooks.put stateIdx)
]
]
+
+useRenderCount
+ :: forall m a
+ . MonadEffect m
+ => Hooked m a (UseEffect (UseRef Int a)) Int
+useRenderCount = Hooks.do
+ renders /\ rendersRef <- Hooks.useRef 1
+ Hooks.captures {} Hooks.useTickEffect do
+ liftEffect (Ref.modify_ (_ + 1) rendersRef)
+ mempty
+ Hooks.pure renders
\ No newline at end of file
diff --git a/recipes/ComponentsMultiTypeReactHooks/.gitignore b/recipes/ComponentsMultiTypeReactHooks/.gitignore
new file mode 100644
index 00000000..57030e7b
--- /dev/null
+++ b/recipes/ComponentsMultiTypeReactHooks/.gitignore
@@ -0,0 +1,15 @@
+/bower_components/
+/node_modules/
+/.pulp-cache/
+/output/
+/generated-docs/
+/.psc-package/
+/.psc*
+/.purs*
+/.psa*
+/.spago
+/.cache/
+/dist/
+/web-dist/
+/prod-dist/
+/prod/
diff --git a/recipes/ComponentsMultiTypeReactHooks/README.md b/recipes/ComponentsMultiTypeReactHooks/README.md
new file mode 100644
index 00000000..5e97094f
--- /dev/null
+++ b/recipes/ComponentsMultiTypeReactHooks/README.md
@@ -0,0 +1,15 @@
+# ComponentsMultiTypeReactHooks
+
+Demonstrates a parent component with several children components, each with different prop types.
+
+## Expected Behavior:
+
+### Browser
+
+A toggle button, a count button, and an input field are the children of the parent. The user can modify the state of any of those inputs and then click the "Check now" button. This will update the most recent observed state.
+The parent will also display how many times it has been rendered.
+
+## Dependencies Used:
+
+[react](https://www.npmjs.com/package/react)
+[react-dom](https://www.npmjs.com/package/react-dom)
diff --git a/recipes/ComponentsMultiTypeReactHooks/spago.dhall b/recipes/ComponentsMultiTypeReactHooks/spago.dhall
new file mode 100644
index 00000000..e6b4b2e9
--- /dev/null
+++ b/recipes/ComponentsMultiTypeReactHooks/spago.dhall
@@ -0,0 +1,11 @@
+{ name = "ComponentsMultiTypeReactHooks"
+, dependencies =
+ [ "console"
+ , "effect"
+ , "psci-support"
+ , "react-basic-hooks"
+ , "react-basic-dom"
+ ]
+, packages = ../../packages.dhall
+, sources = [ "recipes/ComponentsMultiTypeReactHooks/src/**/*.purs" ]
+}
diff --git a/recipes/ComponentsMultiTypeReactHooks/src/Main.purs b/recipes/ComponentsMultiTypeReactHooks/src/Main.purs
new file mode 100644
index 00000000..5bf5478c
--- /dev/null
+++ b/recipes/ComponentsMultiTypeReactHooks/src/Main.purs
@@ -0,0 +1,156 @@
+module ComponentsMultiTypeReactHooks.Main where
+
+import Prelude
+import Data.Maybe (Maybe(..), fromMaybe)
+import Data.Tuple (fst)
+import Effect (Effect)
+import Effect.Exception (throw)
+import Effect.Unsafe (unsafePerformEffect)
+import React.Basic.DOM (render)
+import React.Basic.DOM as R
+import React.Basic.DOM.Events (targetValue)
+import React.Basic.Events (EventHandler, handler, handler_)
+import React.Basic.Hooks
+ ( type (/\)
+ , Component
+ , Hook
+ , Render
+ , UseEffect
+ , UseRef
+ , UseState
+ , component
+ , readRef
+ , useEffectAlways
+ , useRef
+ , useState
+ , useState'
+ , writeRef
+ , (/\)
+ )
+import React.Basic.Hooks as React
+import Web.HTML (window)
+import Web.HTML.HTMLDocument (body)
+import Web.HTML.HTMLElement (toElement)
+import Web.HTML.Window (document)
+
+main :: Effect Unit
+main = do
+ body <- body =<< document =<< window
+ case body of
+ Nothing -> throw "Could not find body."
+ Just b -> do
+ container <- mkContainer
+ render (container unit) (toElement b)
+
+mkContainer :: Component Unit
+mkContainer = do
+ componentA <- mkComponentA
+ componentB <- mkComponentB
+ componentC <- mkComponentC
+ component "Container" \_ -> React.do
+ parentRenders <- useRenderCount
+ observedState /\ setObservedState <-
+ useState' { a: Nothing, b: Nothing, c: Nothing }
+ enabledState <- useState false
+ countState <- useState 0
+ inputState <- useInput "Hello"
+ pure
+ $ R.div_
+ [ R.div
+ { className: "box"
+ , children:
+ [ R.h1_ [ R.text "Component A" ]
+ , componentA enabledState
+ ]
+ }
+ , R.div
+ { className: "box"
+ , children:
+ [ R.h1_ [ R.text "Component B" ]
+ , componentB countState
+ ]
+ }
+ , R.div
+ { className: "box"
+ , children:
+ [ R.h1_ [ R.text "Component C" ]
+ , componentC inputState
+ ]
+ }
+ , R.p_
+ [ R.text "Last observed states:" ]
+ , R.ul_
+ [ R.li_ [ R.text ("Component A: " <> show observedState.a) ]
+ , R.li_ [ R.text ("Component B: " <> show observedState.b) ]
+ , R.li_ [ R.text ("Component C: " <> show observedState.c) ]
+ ]
+ , R.button
+ { onClick:
+ handler_ do
+ setObservedState
+ { a: Just (fst enabledState)
+ , b: Just (fst countState)
+ , c: Just (fst inputState)
+ }
+ , children: [ R.text "Check states now" ]
+ }
+ , R.p_
+ [ R.text ("Parent has been rendered " <> show parentRenders <> " time(s)") ]
+ ]
+
+type SetState state
+ = (state -> state) -> Effect Unit
+
+mkComponentA :: Component (Boolean /\ SetState Boolean)
+mkComponentA =
+ component "ComponentA" \(enabled /\ setEnabled) ->
+ pure
+ $ R.div_
+ [ R.p_ [ R.text "Toggle me!" ]
+ , R.button
+ { onClick: handler_ (setEnabled not)
+ , children: [ R.text (if enabled then "On" else "Off") ]
+ }
+ ]
+
+mkComponentB :: Component (Int /\ SetState Int)
+mkComponentB =
+ component "ComponentB" \(count /\ setCount) ->
+ pure
+ $ R.div_
+ [ R.p_
+ [ R.text "Current value: "
+ , R.strong_ [ R.text (show count) ]
+ ]
+ , R.button
+ { onClick: handler_ (setCount (_ + 1))
+ , children: [ R.text "Increment" ]
+ }
+ ]
+
+mkComponentC :: Component (String /\ EventHandler)
+mkComponentC =
+ component "ComponentC" \(value /\ onChange) ->
+ pure
+ $ R.label_
+ [ R.p_ [ R.text "What do you have to say?" ]
+ , R.input { value, onChange }
+ ]
+
+useInput :: String -> Hook (UseState String) (String /\ EventHandler)
+useInput initialValue = React.do
+ state /\ setState <- useState' initialValue
+ let
+ onChange = handler targetValue \t -> setState (fromMaybe "" t)
+ pure (state /\ onChange)
+
+useRenderCount :: forall a. Render a (UseEffect Unit (UseRef Int a)) Int
+useRenderCount = React.do
+ rendersRef <- useRef 1
+ useEffectAlways do
+ renders <- readRef rendersRef
+ writeRef rendersRef (renders + 1)
+ pure mempty
+ let
+ renders = unsafePerformEffect (readRef rendersRef)
+ pure renders
diff --git a/recipes/ComponentsMultiTypeReactHooks/web/index.html b/recipes/ComponentsMultiTypeReactHooks/web/index.html
new file mode 100644
index 00000000..12f58f91
--- /dev/null
+++ b/recipes/ComponentsMultiTypeReactHooks/web/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ ComponentsMultiTypeReactHooks
+
+
+
+
+
+
+
diff --git a/recipes/ComponentsMultiTypeReactHooks/web/index.js b/recipes/ComponentsMultiTypeReactHooks/web/index.js
new file mode 100644
index 00000000..c92f7865
--- /dev/null
+++ b/recipes/ComponentsMultiTypeReactHooks/web/index.js
@@ -0,0 +1,2 @@
+"use strict";
+require("../../../output/ComponentsMultiTypeReactHooks.Main/index.js").main();
diff --git a/recipes/ComponentsReactHooks/.gitignore b/recipes/ComponentsReactHooks/.gitignore
new file mode 100644
index 00000000..57030e7b
--- /dev/null
+++ b/recipes/ComponentsReactHooks/.gitignore
@@ -0,0 +1,15 @@
+/bower_components/
+/node_modules/
+/.pulp-cache/
+/output/
+/generated-docs/
+/.psc-package/
+/.psc*
+/.purs*
+/.psa*
+/.spago
+/.cache/
+/dist/
+/web-dist/
+/prod-dist/
+/prod/
diff --git a/recipes/ComponentsReactHooks/README.md b/recipes/ComponentsReactHooks/README.md
new file mode 100644
index 00000000..a1af9de8
--- /dev/null
+++ b/recipes/ComponentsReactHooks/README.md
@@ -0,0 +1,15 @@
+# ComponentsReactHooks
+
+Demonstrates how to nest one React Hooks-based component inside another and send props from the parent to the child component.
+
+## Expected Behavior:
+
+### Browser
+
+Clicking the "On/Off" button in the child will toggle the button's label and update the parent component's toggle count. If the user clicks the "Check now" button in the parent, the parent will update its cache of the button state.
+The parent will also display how many times it has been rendered.
+
+## Dependencies Used:
+
+[react](https://www.npmjs.com/package/react)
+[react-dom](https://www.npmjs.com/package/react-dom)
diff --git a/recipes/ComponentsReactHooks/spago.dhall b/recipes/ComponentsReactHooks/spago.dhall
new file mode 100644
index 00000000..f217e4e7
--- /dev/null
+++ b/recipes/ComponentsReactHooks/spago.dhall
@@ -0,0 +1,11 @@
+{ name = "ComponentsReactHooks"
+, dependencies =
+ [ "console"
+ , "effect"
+ , "psci-support"
+ , "react-basic-hooks"
+ , "react-basic-dom"
+ ]
+, packages = ../../packages.dhall
+, sources = [ "recipes/ComponentsReactHooks/src/**/*.purs" ]
+}
diff --git a/recipes/ComponentsReactHooks/src/Main.purs b/recipes/ComponentsReactHooks/src/Main.purs
new file mode 100644
index 00000000..b389f276
--- /dev/null
+++ b/recipes/ComponentsReactHooks/src/Main.purs
@@ -0,0 +1,92 @@
+module ComponentsReactHooks.Main where
+
+import Prelude
+import Data.Maybe (Maybe(..), maybe)
+import Effect (Effect)
+import Effect.Exception (throw)
+import Effect.Unsafe (unsafePerformEffect)
+import React.Basic.DOM (render)
+import React.Basic.DOM as R
+import React.Basic.Events (EventHandler, handler_)
+import React.Basic.Hooks
+ ( Component
+ , Render
+ , UseEffect
+ , UseRef
+ , component
+ , readRef
+ , useEffectAlways
+ , useRef
+ , useState
+ , writeRef
+ , (/\)
+ )
+import React.Basic.Hooks as React
+import Web.HTML (window)
+import Web.HTML.HTMLDocument (body)
+import Web.HTML.HTMLElement (toElement)
+import Web.HTML.Window (document)
+
+main :: Effect Unit
+main = do
+ body <- body =<< document =<< window
+ case body of
+ Nothing -> throw "Could not find body."
+ Just b -> do
+ container <- mkContainer
+ render (container unit) (toElement b)
+
+mkContainer :: Component Unit
+mkContainer = do
+ button <- mkButton
+ component "Container" \_ -> React.do
+ parentRenders <- useRenderCount
+ count /\ setCount <- useState 0
+ enabled /\ setEnabled <- useState false
+ buttonState /\ setButtonState <- useState Nothing
+ let
+ handleClick =
+ handler_ do
+ setCount (_ + 1)
+ setEnabled not
+ pure
+ $ R.div_
+ [ button { enabled, handleClick }
+ , R.p_
+ [ R.text ("Button has been toggled " <> show count <> " time(s)") ]
+ , R.p_
+ [ R.text
+ $ "Last time I checked, the button was: "
+ <> (maybe "(not checked yet)" (if _ then "on" else "off") buttonState)
+ <> ". "
+ , R.button
+ { onClick: handler_ (setButtonState \_ -> Just enabled)
+ , children: [ R.text "Check now" ]
+ }
+ ]
+ , R.p_
+ [ R.text ("Parent has been rendered " <> show parentRenders <> " time(s)") ]
+ ]
+
+mkButton :: Component { enabled :: Boolean, handleClick :: EventHandler }
+mkButton =
+ component "Button" \props -> React.do
+ let
+ label = if props.enabled then "On" else "Off"
+ pure
+ $ R.button
+ { title: label
+ , onClick: props.handleClick
+ , children: [ R.text label ]
+ }
+
+useRenderCount :: forall a. Render a (UseEffect Unit (UseRef Int a)) Int
+useRenderCount = React.do
+ rendersRef <- useRef 1
+ useEffectAlways do
+ renders <- readRef rendersRef
+ writeRef rendersRef (renders + 1)
+ pure mempty
+ let
+ renders = unsafePerformEffect (readRef rendersRef)
+ pure renders
diff --git a/recipes/ComponentsReactHooks/web/index.html b/recipes/ComponentsReactHooks/web/index.html
new file mode 100644
index 00000000..0d7f1885
--- /dev/null
+++ b/recipes/ComponentsReactHooks/web/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ ComponentsReactHooks
+
+
+
+
+
+
+
diff --git a/recipes/ComponentsReactHooks/web/index.js b/recipes/ComponentsReactHooks/web/index.js
new file mode 100644
index 00000000..bb0e041e
--- /dev/null
+++ b/recipes/ComponentsReactHooks/web/index.js
@@ -0,0 +1,2 @@
+"use strict";
+require("../../../output/ComponentsReactHooks.Main/index.js").main();