Skip to content

Commit

Permalink
React-basic 10
Browse files Browse the repository at this point in the history
  • Loading branch information
megamaddu committed Aug 2, 2019
1 parent a76aa48 commit 429f41d
Show file tree
Hide file tree
Showing 7 changed files with 384 additions and 402 deletions.
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# react-basic-hooks [![CircleCI](https://circleci.com/gh/spicydonuts/purescript-react-basic-hooks.svg?style=svg)](https://circleci.com/gh/spicydonuts/purescript-react-basic-hooks)

This is library adds React hooks to [react-basic](https://github.com/lumihq/purescript-react-basic).
`react-basic-hooks` adds React hook support to [react-basic](https://github.com/lumihq/purescript-react-basic)!

_Warning:_ This API relies on recent React versions (>= 16.8.0).
_Note:_ This API relies on recent React versions (>= 16.8.0).
For more info on hooks, see [React's documentation](https://reactjs.org/docs/hooks-intro.html).

I recommend using PureScript's new "qualified do" syntax while using this library (it's used in the examples, the `React.do` bits).
I recommend using PureScript's "qualified do" syntax while using this library (it's used in the examples, the `React.do` bits).
It became available in the `0.12.2` compiler release.

This library provides the `React.Basic.Hooks` module, which can completely replace the `React.Basic` module.
Expand All @@ -20,10 +20,13 @@ mkCounter = do
component "Counter" \props -> React.do
counter /\ setCounter <- useState 0
React.pure $ R.button
{ onClick: handler_ $ setCounter (_ + 1)
, children: [ R.text $ "Increment: " <> show counter ]
}
pure
$ R.button
{ onClick: handler_ do
setCounter (_ + 1)
, children:
[ R.text $ "Increment: " <> show counter ]
}
```

More examples:
Expand All @@ -36,7 +39,3 @@ More examples:
- [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)

_A note on Refs:_ The `Ref` type is useful for all kinds of state (anything which shouldn't trigger a render when changed), particularly references to DOM nodes as in the example.
Unfortunately, while this module remains a small extension to the existing react-basic library it won't be possible to pass a `ref` prop to the native DOM components from `React.Basic.DOM`.
In the meantime, use `element (unsafeCreateDOMComponent "div") { ref: elementRef }`.
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"purescript-prelude": "^4.1.0",
"purescript-console": "^4.2.0",
"purescript-effect": "^2.0.0",
"purescript-react-basic": "^8.0.0 || ^9.0.0",
"purescript-react-basic": "^10.0.0",
"purescript-indexed-monad": "^1.0.0",
"purescript-unsafe-reference": "^3.0.1",
"purescript-aff": "^5.1.1"
Expand Down
106 changes: 55 additions & 51 deletions examples/aff/src/AffEx.purs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module AffEx where

import Prelude

import Data.Either (either)
import Data.Maybe (Maybe(..), maybe)
import Effect.Aff (Aff, Milliseconds(..), delay, error, message, throwError)
Expand All @@ -15,19 +14,18 @@ mkAffEx :: CreateComponent {}
mkAffEx = do
-- A component for fetching and rendering a Cat entity.
catDetails <- mkCatDetails

component "AffEx" \props -> React.do
catKey /\ catChooser <- useCatKeyChooser

pure $ R.div
{ style: R.css { display: "flex", flexFlow: "column" }
, children:
pure
$ R.div
{ style: R.css { display: "flex", flexFlow: "column" }
, children:
[ R.h2_ [ R.text "Cat chooser" ]
, R.p_
[ R.text $
"Select a key to fetch! If you get bored (how would you even!?) " <>
"try holding your arrow keys to select really fast! The result " <>
"always matches the chosen key."
[ R.text
$ "Select a key to fetch! If you get bored (how would you even!?) "
<> "try holding your arrow keys to select really fast! The result "
<> "always matches the chosen key."
]
, catChooser
, R.p_
Expand All @@ -36,60 +34,63 @@ mkAffEx = do
Just k -> element catDetails { catKey: k }
]
]
}
}
where
-- This hook packages up some interactive UI and the current
-- selection the user has made via that UI.
useCatKeyChooser :: Hook _ ((Maybe (Key Cat)) /\ JSX)
useCatKeyChooser = React.do
catKey /\ setCatKey <- useState Nothing
let
catChoice key =
R.label_
[ R.input
{ type: "radio"
, name: "cat-key"
, checked: Just key == catKey
, onChange: handler_ do
setCatKey \_ -> Just key
}
, R.text $ showCatKey key
]

showCatKey :: Key Cat -> String
showCatKey (Key key) = "Cat " <> key
-- This hook packages up some interactive UI and the current
-- selection the user has made via that UI.
useCatKeyChooser :: Hook _ ((Maybe (Key Cat)) /\ JSX)
useCatKeyChooser = React.do
catKey /\ setCatKey <- useState Nothing
let
catChoice key =
R.label_
[ R.input
{ type: "radio"
, name: "cat-key"
, checked: Just key == catKey
, onChange:
handler_ do
setCatKey \_ -> Just key
}
, R.text $ showCatKey key
]

pure $ catKey /\ fragment
[ catChoice $ Key "abc"
, catChoice $ Key "def"
, catChoice $ Key "xyz"
]

-- Hooks can't be used conditionally but components can!
-- Not needing to deal with a `Maybe` key simplifies this
-- compoennt a bit.
mkCatDetails :: CreateComponent { catKey :: Key Cat }
mkCatDetails = do
component "CatDetails" \{ catKey } -> React.do
cat <- useAff catKey $ fetch catKey
pure $ R.text $
maybe "Loading..." (either message showCat) cat
where
showCat (Cat { name }) = "A cat named " <> name
showCatKey :: Key Cat -> String
showCatKey (Key key) = "Cat " <> key
pure $ catKey
/\ fragment
[ catChoice $ Key "abc"
, catChoice $ Key "def"
, catChoice $ Key "ghi"
, catChoice $ Key "xyz"
]

-- Hooks can't be used conditionally but components can!
-- Not needing to deal with a `Maybe` key simplifies this
-- compoennt a bit.
mkCatDetails :: CreateComponent { catKey :: Key Cat }
mkCatDetails = do
component "CatDetails" \{ catKey } -> React.do
cat <- useAff catKey $ fetch catKey
pure $ R.text
$ maybe "Loading..." (either message showCat) cat
where
showCat (Cat { name }) = "A cat named " <> name

-- 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
newtype Key entity
= Key String

derive instance eqKey :: Eq (Key entity)

class Fetch entity where
fetch :: Key entity -> Aff entity


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

instance fetchCat :: Fetch Cat where
fetch = case _ of
Expand All @@ -99,6 +100,9 @@ instance fetchCat :: Fetch Cat where
Key "def" -> do
delay $ Milliseconds 600.0
pure $ Cat { name: "Maxi" }
Key "ghi" -> do
delay $ Milliseconds 900.0
pure $ Cat { name: "Chloe" }
_ -> do
delay $ Milliseconds 900.0
throwError $ error "Cat not found (intended example behavior 😅)"
68 changes: 30 additions & 38 deletions examples/context/src/Context.purs
Original file line number Diff line number Diff line change
@@ -1,56 +1,48 @@
module Context where

import Prelude

import Data.Maybe (Maybe(..))
import Effect (Effect)
import React.Basic.DOM as R
import React.Basic.Events (handler_)
import React.Basic.Hooks (Context, CreateComponent, JSX, type (/\), component, contextProvider, createContext, element, fragment, useContext, useState, (/\))
import React.Basic.Hooks (type (/\), CreateComponent, JSX, ReactContext, component, createContext, element, provider, useContext, useState, (/\))
import React.Basic.Hooks as React

mkContext :: CreateComponent {}
mkContext = do

counterContext <- createContext
counterContext <- createContext (0 /\ pure unit)
store <- mkStore counterContext
counter <- mkCounter counterContext

component "Context" \props -> React.do

pure $ element store
{ children:
[ element counter {}
, element counter {}
, element counter {}
]
}

mkStore
:: Context (Int /\ (Effect Unit))
-> CreateComponent { children :: Array JSX }
pure
$ element store
{ children:
[ element counter {}
, element counter {}
, element counter {}
]
}

mkStore ::
ReactContext (Int /\ (Effect Unit)) ->
CreateComponent { children :: Array JSX }
mkStore context = do
component "Store" \{ children } -> React.do
counter /\ setCounter <- useState 0
let increment = setCounter (_ + 1)
pure $
contextProvider context
(counter /\ increment)
(fragment children)

mkCounter
:: Context (Int /\ (Effect Unit))
-> CreateComponent {}
let
increment = setCounter (_ + 1)
pure
$ provider context
(counter /\ increment)
children

mkCounter ::
ReactContext (Int /\ (Effect Unit)) ->
CreateComponent {}
mkCounter counterContext = do
component "Counter" \props -> React.do
mCounter <- useContext counterContext

case mCounter of
Nothing -> do
pure $ R.text "no counter value found"
Just (counter /\ increment) -> do
pure $
R.button
{ onClick: handler_ increment
, children: [ R.text $ "Increment: " <> show counter ]
}
counter /\ increment <- useContext counterContext
pure
$ R.button
{ onClick: handler_ increment
, children: [ R.text $ "Increment: " <> show counter ]
}
Loading

0 comments on commit 429f41d

Please sign in to comment.