From c1b7bfbcd5a71d65db59a2b022f9e1560040f03d Mon Sep 17 00:00:00 2001 From: Peter Murphy <26548438+ptrfrncsmrph@users.noreply.github.com> Date: Sun, 17 Jan 2021 19:48:59 -0500 Subject: [PATCH] Add Components*ReactHooks recipes (#249) * Add ComponentsReactHooks * Add ComponentsInputReactHooks * Add ComponentsMultiTypeReactHooks * Update README * Add render count to ComponentsReactHooks * Add render count to ComponentsInputReactHooks * Add render count to ComponentsMultiTypeReactHooks * Add render count to ComponentsHalogenHooks * Add render count to ComponentsInputHalogenHooks * Add render count to ComponentsMultiTypeHalogenHooks * s/re-rendered/rendered --- README.md | 3 + recipes/ComponentsHalogenHooks/README.md | 1 + recipes/ComponentsHalogenHooks/src/Main.purs | 21 ++- recipes/ComponentsInputHalogenHooks/README.md | 1 + .../ComponentsInputHalogenHooks/src/Main.purs | 18 +- recipes/ComponentsInputReactHooks/.gitignore | 15 ++ recipes/ComponentsInputReactHooks/README.md | 15 ++ recipes/ComponentsInputReactHooks/spago.dhall | 11 ++ .../ComponentsInputReactHooks/src/Main.purs | 84 ++++++++++ .../ComponentsInputReactHooks/web/index.html | 12 ++ .../ComponentsInputReactHooks/web/index.js | 2 + .../ComponentsMultiTypeHalogenHooks/README.md | 1 + .../src/Main.purs | 20 ++- .../ComponentsMultiTypeReactHooks/.gitignore | 15 ++ .../ComponentsMultiTypeReactHooks/README.md | 15 ++ .../ComponentsMultiTypeReactHooks/spago.dhall | 11 ++ .../src/Main.purs | 156 ++++++++++++++++++ .../web/index.html | 12 ++ .../web/index.js | 2 + recipes/ComponentsReactHooks/.gitignore | 15 ++ recipes/ComponentsReactHooks/README.md | 15 ++ recipes/ComponentsReactHooks/spago.dhall | 11 ++ recipes/ComponentsReactHooks/src/Main.purs | 92 +++++++++++ recipes/ComponentsReactHooks/web/index.html | 12 ++ recipes/ComponentsReactHooks/web/index.js | 2 + 25 files changed, 559 insertions(+), 3 deletions(-) create mode 100644 recipes/ComponentsInputReactHooks/.gitignore create mode 100644 recipes/ComponentsInputReactHooks/README.md create mode 100644 recipes/ComponentsInputReactHooks/spago.dhall create mode 100644 recipes/ComponentsInputReactHooks/src/Main.purs create mode 100644 recipes/ComponentsInputReactHooks/web/index.html create mode 100644 recipes/ComponentsInputReactHooks/web/index.js create mode 100644 recipes/ComponentsMultiTypeReactHooks/.gitignore create mode 100644 recipes/ComponentsMultiTypeReactHooks/README.md create mode 100644 recipes/ComponentsMultiTypeReactHooks/spago.dhall create mode 100644 recipes/ComponentsMultiTypeReactHooks/src/Main.purs create mode 100644 recipes/ComponentsMultiTypeReactHooks/web/index.html create mode 100644 recipes/ComponentsMultiTypeReactHooks/web/index.js create mode 100644 recipes/ComponentsReactHooks/.gitignore create mode 100644 recipes/ComponentsReactHooks/README.md create mode 100644 recipes/ComponentsReactHooks/spago.dhall create mode 100644 recipes/ComponentsReactHooks/src/Main.purs create mode 100644 recipes/ComponentsReactHooks/web/index.html create mode 100644 recipes/ComponentsReactHooks/web/index.js 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 @@ + + +
+ +