diff --git a/bower.json b/bower.json
index d0a28e5..7c0d96c 100644
--- a/bower.json
+++ b/bower.json
@@ -1,7 +1,12 @@
{
"name": "purescript-react-basic",
"license": "Apache-2.0",
- "ignore": ["**/.*", "node_modules", "bower_components", "output"],
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "output"
+ ],
"repository": {
"type": "git",
"url": "git://github.com/lumihq/purescript-react-basic.git"
@@ -17,7 +22,9 @@
"purescript-typelevel-prelude": "^3.0.0",
"purescript-unsafe-coerce": "^4.0.0",
"purescript-web-dom": "^1.0.0",
- "purescript-web-events": "^1.0.0"
+ "purescript-web-events": "^1.0.0",
+ "purescript-profunctor-lenses": "^5.0.0",
+ "purescript-indexed-monad": "^1.0.0"
},
"devDependencies": {
"purescript-web-html": "^1.0.0"
diff --git a/examples/component/README.md b/examples/component/README.md
index cc747f4..aef84a0 100644
--- a/examples/component/README.md
+++ b/examples/component/README.md
@@ -2,9 +2,8 @@
## Building
-```
-npm install
-make all
+```sh
+make
```
This will compile the PureScript source files, bundle them, and use Browserify to combine PureScript and NPM sources into a single bundle.
diff --git a/examples/component/package.json b/examples/component/package.json
index 624a6f3..fbcf2be 100644
--- a/examples/component/package.json
+++ b/examples/component/package.json
@@ -1,7 +1,7 @@
{
"dependencies": {
- "react": "^16.4.2",
- "react-dom": "^16.4.2"
+ "react": "16.7.0-alpha.2",
+ "react-dom": "16.7.0-alpha.2"
},
"devDependencies": {
"browserify": "^16.2.2"
diff --git a/examples/component/src/Container.purs b/examples/component/src/Container.purs
index 1e6e877..e960282 100644
--- a/examples/component/src/Container.purs
+++ b/examples/component/src/Container.purs
@@ -2,18 +2,19 @@ module Container where
import Prelude
-import React.Basic (Component, JSX, createComponent, makeStateless)
+import React.Basic (CreateComponent, component, element)
+import React.Basic as React
import React.Basic.DOM as R
-import ToggleButton (toggleButton)
+import ToggleButton (mkToggleButton)
-component :: Component Unit
-component = createComponent "Container"
+mkToggleButtonContainer :: CreateComponent {} Unit
+mkToggleButtonContainer = do
+ toggleButton <- mkToggleButton
-toggleButtonContainer :: JSX
-toggleButtonContainer = unit # makeStateless component \_ ->
- R.div
- { children:
- [ toggleButton { label: "A" }
- , toggleButton { label: "B" }
- ]
- }
+ component "Container" \_ ->
+ React.pure $ R.div
+ { children:
+ [ element toggleButton { label: "A" }
+ , element toggleButton { label: "B" }
+ ]
+ }
diff --git a/examples/component/src/Main.purs b/examples/component/src/Main.purs
index 67193a8..3c32365 100644
--- a/examples/component/src/Main.purs
+++ b/examples/component/src/Main.purs
@@ -2,10 +2,11 @@ module Main where
import Prelude
-import Container (toggleButtonContainer)
+import Container (mkToggleButtonContainer)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Exception (throw)
+import React.Basic (element)
import React.Basic.DOM (render)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
@@ -17,6 +18,7 @@ main = do
container <- getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
case container of
Nothing -> throw "Container element not found."
- Just c ->
- let app = toggleButtonContainer
- in render app c
+ Just c -> do
+ toggleButtonContainer <- mkToggleButtonContainer
+ let app = element toggleButtonContainer {}
+ render app c
diff --git a/examples/component/src/ToggleButton.purs b/examples/component/src/ToggleButton.purs
index 49430ae..d3fea55 100644
--- a/examples/component/src/ToggleButton.purs
+++ b/examples/component/src/ToggleButton.purs
@@ -3,40 +3,24 @@ module ToggleButton where
import Prelude
import Effect.Console (log)
-import React.Basic (Component, JSX, StateUpdate(..), capture_, createComponent, make)
+import React.Basic (CreateComponent, UseEffect, UseState, component, toKey, useEffect, useState, (/\))
+import React.Basic as React
import React.Basic.DOM as R
-
-component :: Component Props
-component = createComponent "ToggleButton"
-
-type Props =
- { label :: String
- }
-
-data Action
- = Toggle
-
-toggleButton :: Props -> JSX
-toggleButton = make component
- { initialState:
- { on: false
+import React.Basic.DOM.Events (capture_)
+
+mkToggleButton :: CreateComponent { label :: String } (UseEffect (UseState Boolean Unit))
+mkToggleButton = do
+ component "ToggleButton" \{ label } -> React.do
+ on /\ setOn <- useState false
+
+ useEffect [toKey on] do
+ log $ "State: " <> if on then "On" else "Off"
+ pure (pure unit)
+
+ React.pure $ R.button
+ { onClick: capture_ $ setOn not
+ , children:
+ [ R.text label
+ , R.text if on then " On" else " Off"
+ ]
}
-
- , update: \self -> case _ of
- Toggle ->
- UpdateAndSideEffects
- self.state { on = not self.state.on }
- \nextSelf -> do
- log $ "next state: " <> show nextSelf.state
-
- , render: \self ->
- R.button
- { onClick: capture_ self Toggle
- , children:
- [ R.text self.props.label
- , R.text if self.state.on
- then " On"
- else " Off"
- ]
- }
- }
diff --git a/examples/controlled-input/README.md b/examples/controlled-input/README.md
index 2004b87..a36ddf3 100644
--- a/examples/controlled-input/README.md
+++ b/examples/controlled-input/README.md
@@ -2,9 +2,8 @@
## Building
-```
-npm install
-make all
+```sh
+make
```
This will compile the PureScript source files, bundle them, and use Browserify to combine PureScript and NPM sources into a single bundle.
diff --git a/examples/controlled-input/package.json b/examples/controlled-input/package.json
index 624a6f3..fbcf2be 100644
--- a/examples/controlled-input/package.json
+++ b/examples/controlled-input/package.json
@@ -1,7 +1,7 @@
{
"dependencies": {
- "react": "^16.4.2",
- "react-dom": "^16.4.2"
+ "react": "16.7.0-alpha.2",
+ "react-dom": "16.7.0-alpha.2"
},
"devDependencies": {
"browserify": "^16.2.2"
diff --git a/examples/controlled-input/src/ControlledInput.purs b/examples/controlled-input/src/ControlledInput.purs
index 7824483..845730e 100644
--- a/examples/controlled-input/src/ControlledInput.purs
+++ b/examples/controlled-input/src/ControlledInput.purs
@@ -2,44 +2,51 @@ module ControlledInput where
import Prelude
+import Control.Applicative.Indexed (ipure)
import Data.Maybe (Maybe(..), fromMaybe, maybe)
-import React.Basic (Component, JSX, StateUpdate(..), capture, createComponent, make)
+import React.Basic (CreateComponent, Render, UseState, component, fragment, useState, (/\))
import React.Basic as React
import React.Basic.DOM as R
-import React.Basic.DOM.Events (targetValue, timeStamp)
-import React.Basic.Events (merge)
-
-component :: Component Props
-component = createComponent "ControlledInput"
-
-type Props = Unit
-
-data Action
- = ValueChanged String Number
-
-controlledInput :: Props -> JSX
-controlledInput = make component
- { initialState:
- { value: "hello world"
- , timestamp: Nothing
- }
-
- , update: \self -> case _ of
- ValueChanged value timestamp ->
- Update self.state
- { value = value
- , timestamp = Just timestamp
- }
+import React.Basic.DOM.Events (capture, targetValue, timeStamp)
+import React.Basic.Events (EventHandler, merge)
+
+mkControlledInput :: CreateComponent {} (UseInput () (UseInput () Unit))
+mkControlledInput =
+ component "ControlledInput" \props -> React.do
+ firstName <- useInput "hello"
+ lastName <- useInput "world"
+
+ React.pure $ R.form_
+ [ renderInput firstName
+ , renderInput lastName
+ ]
+ where
+ renderInput input =
+ fragment
+ [ R.input { onChange: input.onChange, value: input.value }
+ , R.p_ [ R.text ("Current value = " <> show input.value) ]
+ , R.p_ [ R.text ("Changed at = " <> maybe "never" show input.lastChanged) ]
+ ]
- , render: \self ->
- React.fragment
- [ R.input
- { onChange:
- capture self (merge { targetValue, timeStamp })
- \{ timeStamp, targetValue } -> ValueChanged (fromMaybe "" targetValue) timeStamp
- , value: self.state.value
+type InputState state = { value :: String, lastChanged :: Maybe Number | state }
+
+type UseInput state hooks = UseState (InputState state) hooks
+useInput
+ :: forall hooks
+ . String
+ -> Render hooks
+ (UseInput () hooks)
+ (InputState ( onChange :: EventHandler ))
+useInput initialValue = React.do
+ { value, lastChanged } /\ replaceState <- useState { value: initialValue, lastChanged: Nothing }
+ ipure
+ { onChange: capture
+ (merge { targetValue, timeStamp })
+ \{ timeStamp, targetValue } -> do
+ replaceState \_ ->
+ { value: fromMaybe "" targetValue
+ , lastChanged: Just timeStamp
}
- , R.p_ [ R.text ("Current value = " <> show self.state.value) ]
- , R.p_ [ R.text ("Changed at = " <> maybe "never" show self.state.timestamp) ]
- ]
- }
+ , value
+ , lastChanged
+ }
diff --git a/examples/controlled-input/src/Main.purs b/examples/controlled-input/src/Main.purs
index 8651738..2af6387 100644
--- a/examples/controlled-input/src/Main.purs
+++ b/examples/controlled-input/src/Main.purs
@@ -2,10 +2,11 @@ module Main where
import Prelude
-import ControlledInput (controlledInput)
+import ControlledInput (mkControlledInput)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Exception (throw)
+import React.Basic (element)
import React.Basic.DOM (render)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
@@ -17,6 +18,7 @@ main = do
container <- getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
case container of
Nothing -> throw "Container element not found."
- Just c ->
- let app = controlledInput unit
- in render app c
+ Just c -> do
+ controlledInput <- mkControlledInput
+ let app = element controlledInput {}
+ render app c
diff --git a/examples/counter/README.md b/examples/counter/README.md
index f2418c0..88847e5 100644
--- a/examples/counter/README.md
+++ b/examples/counter/README.md
@@ -2,9 +2,8 @@
## Building
-```
-npm install
-make all
+```sh
+make
```
This will compile the PureScript source files, bundle them, and use Browserify to combine PureScript and NPM sources into a single bundle.
diff --git a/examples/counter/package.json b/examples/counter/package.json
index 583c22b..4386ff4 100644
--- a/examples/counter/package.json
+++ b/examples/counter/package.json
@@ -1,7 +1,7 @@
{
"dependencies": {
- "react": "16.6.0",
- "react-dom": "16.6.0"
+ "react": "16.7.0-alpha.2",
+ "react-dom": "16.7.0-alpha.2"
},
"devDependencies": {
"browserify": "16.2.3"
diff --git a/examples/counter/src/Counter.js b/examples/counter/src/Counter.js
new file mode 100644
index 0000000..97f35c0
--- /dev/null
+++ b/examples/counter/src/Counter.js
@@ -0,0 +1,7 @@
+"use strict";
+
+exports.setDocumentTitle = function(title) {
+ return function() {
+ document.title = title;
+ };
+};
diff --git a/examples/counter/src/Counter.purs b/examples/counter/src/Counter.purs
index cbea449..9fa9a6e 100644
--- a/examples/counter/src/Counter.purs
+++ b/examples/counter/src/Counter.purs
@@ -2,31 +2,24 @@ module Counter where
import Prelude
-
-import React.Basic (Component, JSX, StateUpdate(..), capture_, createComponent, make)
+import Effect (Effect)
+import React.Basic (CreateComponent, UseEffect, UseState, component, toKey, useEffect, useState, (/\))
+import React.Basic as React
import React.Basic.DOM as R
+import React.Basic.DOM.Events (capture_)
-component :: Component Props
-component = createComponent "Counter"
-
-type Props =
- { label :: String
- }
-
-data Action
- = Increment
+mkCounter :: CreateComponent {} (UseEffect (UseState Int Unit))
+mkCounter = do
+ component "Counter" \props -> React.do
+ counter /\ setCounter <- useState 0
-counter :: Props -> JSX
-counter = make component { initialState, update, render }
- where
- initialState = { counter: 0, dummy: 0 }
+ useEffect [toKey counter] do
+ setDocumentTitle $ "Count: " <> show counter
+ pure mempty
- update self = case _ of
- Increment ->
- Update self.state { counter = self.state.counter + 1 }
+ React.pure $ R.button
+ { onClick: capture_ $ setCounter (_ + 1)
+ , children: [ R.text $ "Increment: " <> show counter ]
+ }
- render self =
- R.button
- { onClick: capture_ self Increment
- , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ]
- }
+foreign import setDocumentTitle :: String -> Effect Unit
diff --git a/examples/counter/src/Main.purs b/examples/counter/src/Main.purs
index fa07fd4..b702611 100644
--- a/examples/counter/src/Main.purs
+++ b/examples/counter/src/Main.purs
@@ -2,10 +2,11 @@ module Main where
import Prelude
-import Counter (counter)
+import Counter (mkCounter)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Exception (throw)
+import React.Basic (element)
import React.Basic.DOM (render)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
@@ -17,6 +18,7 @@ main = do
container <- getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
case container of
Nothing -> throw "Container element not found."
- Just c ->
- let app = counter { label: "Increment" }
- in render app c
+ Just c -> do
+ counter <- mkCounter
+ let app = element counter {}
+ render app c
diff --git a/examples/legacy-v2/README.md b/examples/legacy-v2/README.md
index f2418c0..7e1799b 100644
--- a/examples/legacy-v2/README.md
+++ b/examples/legacy-v2/README.md
@@ -1,10 +1,9 @@
-# Counter Example
+# Legacy Example
## Building
-```
-npm install
-make all
+```sh
+make
```
This will compile the PureScript source files, bundle them, and use Browserify to combine PureScript and NPM sources into a single bundle.
diff --git a/examples/legacy-v2/package.json b/examples/legacy-v2/package.json
index 624a6f3..4386ff4 100644
--- a/examples/legacy-v2/package.json
+++ b/examples/legacy-v2/package.json
@@ -1,9 +1,9 @@
{
"dependencies": {
- "react": "^16.4.2",
- "react-dom": "^16.4.2"
+ "react": "16.7.0-alpha.2",
+ "react-dom": "16.7.0-alpha.2"
},
"devDependencies": {
- "browserify": "^16.2.2"
+ "browserify": "16.2.3"
}
}
diff --git a/examples/legacy-v2/src/Compat.purs b/examples/legacy-v2/src/Compat.purs
deleted file mode 100644
index 30a32cc..0000000
--- a/examples/legacy-v2/src/Compat.purs
+++ /dev/null
@@ -1,49 +0,0 @@
-module React.Basic.Compat
- ( Component
- , component
- , stateless
- , module React.Basic
- ) where
-
-import Prelude
-
-import Effect (Effect)
-import React.Basic (JSX, ReactComponent, Self, StateUpdate(..), createComponent, element, elementKeyed, empty, fragment, make, makeStateless, send, toReactComponent)
-
-type Component = ReactComponent
-
--- | Supports a common subset of the v2 API to ease the upgrade process
-component
- :: forall props state
- . { displayName :: String
- , initialState :: { | state }
- , receiveProps :: { props :: { | props }, state :: { | state }, setState :: ({ | state } -> { | state }) -> Effect Unit } -> Effect Unit
- , render :: { props :: { | props }, state :: { | state }, setState :: ({ | state } -> { | state }) -> Effect Unit } -> JSX
- }
- -> ReactComponent { | props }
-component { displayName, initialState, receiveProps, render } =
- toReactComponent identity (createComponent displayName)
- { initialState: initialState
- , didMount: receiveProps <<< selfToLegacySelf
- , didUpdate: receiveProps <<< selfToLegacySelf
- , update: \self stateUpdate -> Update (stateUpdate self.state)
- , render: render <<< selfToLegacySelf
- }
- where
- selfToLegacySelf self@{ props, state } =
- { props
- , state
- , setState: send self
- }
-
--- | Supports a common subset of the v2 API to ease the upgrade process
-stateless
- :: forall props
- . { displayName :: String
- , render :: { | props } -> JSX
- }
- -> ReactComponent { | props }
-stateless { displayName, render } =
- toReactComponent identity (createComponent displayName)
- { render: \self -> render self.props
- }
diff --git a/examples/legacy-v2/src/LegacyCounter.purs b/examples/legacy-v2/src/LegacyCounter.purs
index 74d8e26..b578ccb 100644
--- a/examples/legacy-v2/src/LegacyCounter.purs
+++ b/examples/legacy-v2/src/LegacyCounter.purs
@@ -2,7 +2,7 @@ module LegacyCounter where
import Prelude
-import React.Basic.Compat (Component, component, element, stateless)
+import React.Basic.Compat (Component, UseStatefulComponent, component, element, stateless)
import React.Basic.DOM as R
import React.Basic.Events as Events
@@ -11,7 +11,7 @@ type Props =
}
-- | checks `component`
-legacyCounter :: Component Props
+legacyCounter :: Component Props (UseStatefulComponent { counter :: Int })
legacyCounter = component { displayName: "LegacyCounter", initialState, receiveProps, render }
where
initialState =
@@ -29,7 +29,7 @@ legacyCounter = component { displayName: "LegacyCounter", initialState, receiveP
}
-- | checks `stateless`
-buttonLabel :: Component { label :: String, counter :: Int }
+buttonLabel :: Component { label :: String, counter :: Int } Unit
buttonLabel = stateless { displayName: "ButtonLabel", render }
where
render props =
diff --git a/examples/reducer/.gitignore b/examples/reducer/.gitignore
new file mode 100644
index 0000000..645684d
--- /dev/null
+++ b/examples/reducer/.gitignore
@@ -0,0 +1,4 @@
+output
+html/index.js
+package-lock.json
+node_modules
diff --git a/examples/reducer/Makefile b/examples/reducer/Makefile
new file mode 100644
index 0000000..ecacfbe
--- /dev/null
+++ b/examples/reducer/Makefile
@@ -0,0 +1,8 @@
+all: node_modules
+ purs compile src/*.purs '../../src/**/*.purs' '../../bower_components/purescript-*/src/**/*.purs'
+ purs bundle -m Main --main Main output/*/*.js > output/bundle.js
+ node_modules/.bin/browserify output/bundle.js -o html/index.js
+
+node_modules:
+ npm install
+
diff --git a/examples/reducer/README.md b/examples/reducer/README.md
new file mode 100644
index 0000000..8c2ad2e
--- /dev/null
+++ b/examples/reducer/README.md
@@ -0,0 +1,11 @@
+# Reducer Example
+
+## Building
+
+```sh
+make
+```
+
+This will compile the PureScript source files, bundle them, and use Browserify to combine PureScript and NPM sources into a single bundle.
+
+Then open `html/index.html` in your browser.
diff --git a/examples/reducer/html/index.html b/examples/reducer/html/index.html
new file mode 100644
index 0000000..6b93b7c
--- /dev/null
+++ b/examples/reducer/html/index.html
@@ -0,0 +1,10 @@
+
+
+
+ react-basic example
+
+
+
+
+
+
diff --git a/examples/reducer/package.json b/examples/reducer/package.json
new file mode 100644
index 0000000..4386ff4
--- /dev/null
+++ b/examples/reducer/package.json
@@ -0,0 +1,9 @@
+{
+ "dependencies": {
+ "react": "16.7.0-alpha.2",
+ "react-dom": "16.7.0-alpha.2"
+ },
+ "devDependencies": {
+ "browserify": "16.2.3"
+ }
+}
diff --git a/examples/reducer/src/Main.purs b/examples/reducer/src/Main.purs
new file mode 100644
index 0000000..0c52b92
--- /dev/null
+++ b/examples/reducer/src/Main.purs
@@ -0,0 +1,24 @@
+module Main where
+
+import Prelude
+
+import Reducer (mkReducer)
+import Data.Maybe (Maybe(..))
+import Effect (Effect)
+import Effect.Exception (throw)
+import React.Basic (element)
+import React.Basic.DOM (render)
+import Web.DOM.NonElementParentNode (getElementById)
+import Web.HTML (window)
+import Web.HTML.HTMLDocument (toNonElementParentNode)
+import Web.HTML.Window (document)
+
+main :: Effect Unit
+main = do
+ container <- getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
+ case container of
+ Nothing -> throw "Container element not found."
+ Just c -> do
+ reducer <- mkReducer
+ let app = element reducer {}
+ render app c
diff --git a/examples/reducer/src/Reducer.purs b/examples/reducer/src/Reducer.purs
new file mode 100644
index 0000000..36778bd
--- /dev/null
+++ b/examples/reducer/src/Reducer.purs
@@ -0,0 +1,35 @@
+module Reducer where
+
+import Prelude
+
+import React.Basic (CreateComponent, UseReducer, component, fragment, useReducer, (/\))
+import React.Basic as React
+import React.Basic.DOM as R
+import React.Basic.DOM.Events (capture_)
+
+data Action
+ = Increment
+ | Decrement
+
+mkReducer :: CreateComponent {} (UseReducer { counter :: Int } Action Unit)
+mkReducer = do
+ component "Reducer" \props -> React.do
+
+ state /\ dispatch <-
+ useReducer { counter: 0 } \state -> case _ of
+ Increment -> state { counter = state.counter + 1 }
+ Decrement -> state { counter = state.counter - 1 }
+
+ React.pure $ fragment
+ [ R.button
+ { onClick: capture_ $ dispatch Decrement
+ , children: [ R.text $ "Decrement" ]
+ }
+ , R.button
+ { onClick: capture_ $ dispatch Increment
+ , children: [ R.text $ "Increment" ]
+ }
+ , R.div_
+ [ R.text $ show state.counter
+ ]
+ ]
diff --git a/examples/refs/.gitignore b/examples/refs/.gitignore
new file mode 100644
index 0000000..645684d
--- /dev/null
+++ b/examples/refs/.gitignore
@@ -0,0 +1,4 @@
+output
+html/index.js
+package-lock.json
+node_modules
diff --git a/examples/refs/Makefile b/examples/refs/Makefile
new file mode 100644
index 0000000..ecacfbe
--- /dev/null
+++ b/examples/refs/Makefile
@@ -0,0 +1,8 @@
+all: node_modules
+ purs compile src/*.purs '../../src/**/*.purs' '../../bower_components/purescript-*/src/**/*.purs'
+ purs bundle -m Main --main Main output/*/*.js > output/bundle.js
+ node_modules/.bin/browserify output/bundle.js -o html/index.js
+
+node_modules:
+ npm install
+
diff --git a/examples/refs/README.md b/examples/refs/README.md
new file mode 100644
index 0000000..baa86ae
--- /dev/null
+++ b/examples/refs/README.md
@@ -0,0 +1,11 @@
+# Refs Example
+
+## Building
+
+```sh
+make
+```
+
+This will compile the PureScript source files, bundle them, and use Browserify to combine PureScript and NPM sources into a single bundle.
+
+Then open `html/index.html` in your browser.
diff --git a/examples/refs/html/index.html b/examples/refs/html/index.html
new file mode 100644
index 0000000..6b93b7c
--- /dev/null
+++ b/examples/refs/html/index.html
@@ -0,0 +1,10 @@
+
+
+
+ react-basic example
+
+
+
+
+
+
diff --git a/examples/refs/package.json b/examples/refs/package.json
new file mode 100644
index 0000000..4386ff4
--- /dev/null
+++ b/examples/refs/package.json
@@ -0,0 +1,9 @@
+{
+ "dependencies": {
+ "react": "16.7.0-alpha.2",
+ "react-dom": "16.7.0-alpha.2"
+ },
+ "devDependencies": {
+ "browserify": "16.2.3"
+ }
+}
diff --git a/examples/refs/src/Main.purs b/examples/refs/src/Main.purs
new file mode 100644
index 0000000..ec70e25
--- /dev/null
+++ b/examples/refs/src/Main.purs
@@ -0,0 +1,24 @@
+module Main where
+
+import Prelude
+
+import Refs (mkRefs)
+import Data.Maybe (Maybe(..))
+import Effect (Effect)
+import Effect.Exception (throw)
+import React.Basic (element)
+import React.Basic.DOM (render)
+import Web.DOM.NonElementParentNode (getElementById)
+import Web.HTML (window)
+import Web.HTML.HTMLDocument (toNonElementParentNode)
+import Web.HTML.Window (document)
+
+main :: Effect Unit
+main = do
+ container <- getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
+ case container of
+ Nothing -> throw "Container element not found."
+ Just c -> do
+ refs <- mkRefs
+ let app = element refs {}
+ render app c
diff --git a/examples/refs/src/Refs.purs b/examples/refs/src/Refs.purs
new file mode 100644
index 0000000..7fd4a3f
--- /dev/null
+++ b/examples/refs/src/Refs.purs
@@ -0,0 +1,90 @@
+module Refs where
+
+import Prelude
+
+import Data.Int (round)
+import Data.Maybe (Maybe(..))
+import Data.Nullable (Nullable, null)
+import Math (pow, sqrt)
+import React.Basic (CreateComponent, Ref, Render, UseEffect, UseRef, UseState, Tuple, component, fragment, readRefMaybe, useEffect, useRef, useState, (/\))
+import React.Basic as React
+import React.Basic.DOM as R
+import Unsafe.Coerce (unsafeCoerce)
+import Web.DOM (Node)
+import Web.Event.Event (EventType(..))
+import Web.Event.EventTarget (addEventListener, eventListener, removeEventListener)
+import Web.HTML (window)
+import Web.HTML.HTMLElement (getBoundingClientRect)
+import Web.HTML.HTMLElement as HTMLElement
+import Web.HTML.Window as Window
+
+mkRefs :: CreateComponent {} (UseNodeDistance (UseNodeDistance (UseNodeDistance Unit)))
+mkRefs = do
+ component "Refs" \props -> React.do
+
+ mouseDistance1 /\ buttonRef1 <- useNodeDistanceFromMouse
+ mouseDistance2 /\ buttonRef2 <- useNodeDistanceFromMouse
+ mouseDistance3 /\ buttonRef3 <- useNodeDistanceFromMouse
+
+ React.pure $ fragment
+ [ R.button
+ { ref: buttonRef1
+ , children: [ R.text $ show mouseDistance1 <> "px" ]
+ , style: R.css { width: "100px", position: "absolute", top: "20px", left: "200px" }
+ }
+ , R.button
+ { ref: buttonRef2
+ , children: [ R.text $ show mouseDistance2 <> "px" ]
+ , style: R.css { width: "100px", position: "absolute", top: "60px", left: "40px" }
+ }
+ , R.button
+ { ref: buttonRef3
+ , children: [ R.text $ show mouseDistance3 <> "px" ]
+ , style: R.css { width: "100px", position: "absolute", top: "120px", left: "90px" }
+ }
+ ]
+
+type UseNodeDistance hooks = UseEffect (UseState Int (UseRef (Nullable Node) hooks))
+
+useNodeDistanceFromMouse
+ :: forall hooks
+ . Render
+ hooks
+ (UseNodeDistance hooks)
+ (Tuple Int (Ref (Nullable Node)))
+useNodeDistanceFromMouse = React.do
+ elementRef <- useRef null
+ mouseDistance /\ setMouseDistance <- useState 0
+
+ useEffect [] do
+ maybeElement <- map (HTMLElement.fromNode =<< _) (readRefMaybe elementRef)
+ case maybeElement of
+ Nothing -> pure (pure unit)
+ Just element -> do
+ mouseMoveListener <- eventListener \e -> do
+ { top, bottom, left, right } <- getBoundingClientRect element
+ let
+ mouseX = (unsafeCoerce e).clientX
+ mouseY = (unsafeCoerce e).clientY
+ distanceX =
+ if mouseX # between left right
+ then 0.0
+ else if mouseX < left
+ then left - mouseX
+ else mouseX - right
+ distanceY =
+ if mouseY # between top bottom
+ then 0.0
+ else if mouseY < top
+ then top - mouseY
+ else mouseY - bottom
+ distance = sqrt ((distanceX `pow` 2.0) + (distanceY `pow` 2.0))
+ setMouseDistance \_ -> round distance
+
+ let mouseMoveEventType = EventType "mousemove"
+ windowEventTarget <- map Window.toEventTarget window
+ addEventListener mouseMoveEventType mouseMoveListener false windowEventTarget
+ pure do
+ removeEventListener mouseMoveEventType mouseMoveListener false windowEventTarget
+
+ React.pure (mouseDistance /\ elementRef)
diff --git a/examples/todo-app/.gitignore b/examples/todo-app/.gitignore
new file mode 100644
index 0000000..645684d
--- /dev/null
+++ b/examples/todo-app/.gitignore
@@ -0,0 +1,4 @@
+output
+html/index.js
+package-lock.json
+node_modules
diff --git a/examples/todo-app/Makefile b/examples/todo-app/Makefile
new file mode 100644
index 0000000..ecacfbe
--- /dev/null
+++ b/examples/todo-app/Makefile
@@ -0,0 +1,8 @@
+all: node_modules
+ purs compile src/*.purs '../../src/**/*.purs' '../../bower_components/purescript-*/src/**/*.purs'
+ purs bundle -m Main --main Main output/*/*.js > output/bundle.js
+ node_modules/.bin/browserify output/bundle.js -o html/index.js
+
+node_modules:
+ npm install
+
diff --git a/examples/todo-app/README.md b/examples/todo-app/README.md
new file mode 100644
index 0000000..55d0bc0
--- /dev/null
+++ b/examples/todo-app/README.md
@@ -0,0 +1,11 @@
+# Todo App Example
+
+## Building
+
+```sh
+make
+```
+
+This will compile the PureScript source files, bundle them, and use Browserify to combine PureScript and NPM sources into a single bundle.
+
+Then open `html/index.html` in your browser.
diff --git a/examples/todo-app/html/index.html b/examples/todo-app/html/index.html
new file mode 100644
index 0000000..6b93b7c
--- /dev/null
+++ b/examples/todo-app/html/index.html
@@ -0,0 +1,10 @@
+
+
+
+ react-basic example
+
+
+
+
+
+
diff --git a/examples/todo-app/package.json b/examples/todo-app/package.json
new file mode 100644
index 0000000..80aec4d
--- /dev/null
+++ b/examples/todo-app/package.json
@@ -0,0 +1,10 @@
+{
+ "dependencies": {
+ "react": "16.7.0-alpha.2",
+ "react-dom": "16.7.0-alpha.2",
+ "uuid": "3.3.2"
+ },
+ "devDependencies": {
+ "browserify": "16.2.3"
+ }
+}
diff --git a/examples/todo-app/src/Main.purs b/examples/todo-app/src/Main.purs
new file mode 100644
index 0000000..86e4dac
--- /dev/null
+++ b/examples/todo-app/src/Main.purs
@@ -0,0 +1,24 @@
+module Main where
+
+import Prelude
+
+import TodoApp (mkTodoApp)
+import Data.Maybe (Maybe(..))
+import Effect (Effect)
+import Effect.Exception (throw)
+import React.Basic (element)
+import React.Basic.DOM (render)
+import Web.DOM.NonElementParentNode (getElementById)
+import Web.HTML (window)
+import Web.HTML.HTMLDocument (toNonElementParentNode)
+import Web.HTML.Window (document)
+
+main :: Effect Unit
+main = do
+ container <- getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
+ case container of
+ Nothing -> throw "Container element not found."
+ Just c -> do
+ todoApp <- mkTodoApp
+ let app = element todoApp {}
+ render app c
diff --git a/examples/todo-app/src/TodoApp.purs b/examples/todo-app/src/TodoApp.purs
new file mode 100644
index 0000000..2b34c5b
--- /dev/null
+++ b/examples/todo-app/src/TodoApp.purs
@@ -0,0 +1,159 @@
+module TodoApp where
+
+import Prelude
+
+import Data.Array as Array
+import Data.Foldable (traverse_)
+import Data.Maybe (Maybe(..))
+import Effect (Effect)
+import React.Basic (CreateComponent, UseReducer, UseState, component, element, elementKeyed, empty, memo, useReducer, useState, (/\))
+import React.Basic as React
+import React.Basic.DOM as R
+import React.Basic.DOM.Events (capture, capture_, targetValue)
+import React.Basic.Events as Events
+
+data Action
+ = CreateTodo String
+ | ToggleTodo Int
+ | DeleteTodo Int
+ | SetFilter TodoFilter
+
+data TodoFilter
+ = All
+ | Complete
+ | Incomplete
+derive instance eqTodoFilter :: Eq TodoFilter
+
+type Todo =
+ { task :: String
+ , isComplete :: Boolean
+ }
+
+type State =
+ { todos :: Array Todo
+ , filter :: TodoFilter
+ }
+
+reducer :: State -> Action -> State
+reducer state = case _ of
+ CreateTodo task ->
+ state { todos = Array.cons { task, isComplete: false } state.todos }
+
+ ToggleTodo index ->
+ case Array.modifyAt index (\todo -> todo { isComplete = not todo.isComplete }) state.todos of
+ Just todos ->
+ state { todos = todos }
+ Nothing ->
+ state
+
+ DeleteTodo index ->
+ case Array.deleteAt index state.todos of
+ Just todos ->
+ state { todos = todos }
+ Nothing ->
+ state
+
+ SetFilter filter ->
+ state { filter = filter }
+
+mkTodoApp :: CreateComponent {} (UseReducer State Action Unit)
+mkTodoApp = do
+ let initialState = { todos: [], filter: All }
+ todoInput <- memo mkTodoInput
+ todoRow <- memo mkTodoRow
+ todoFilters <- memo mkTodoFilters
+
+ component "TodoApp" \props -> React.do
+ state /\ dispatch <- useReducer initialState reducer
+ React.pure $ R.div
+ { children:
+ [ element todoInput { dispatch }
+ , R.div_ $ flip Array.mapWithIndex state.todos \id todo ->
+ if state.filter == All ||
+ (todo.isComplete && state.filter == Complete) ||
+ (not todo.isComplete && state.filter == Incomplete)
+ then elementKeyed todoRow { key: show id, id, todo, dispatch }
+ else empty
+ , element todoFilters { filter: state.filter, dispatch }
+ ]
+ , style: R.css
+ { maxWidth: "600px"
+ , margin: "auto"
+ , padding: "16px"
+ , fontFamily: "sans-serif"
+ , fontSize: "16px"
+ }
+ }
+
+ where
+ todoAppEl = element $ R.unsafeCreateDOMComponent "todo-app"
+
+mkTodoInput :: CreateComponent { dispatch :: Action -> Effect Unit } (UseState String Unit)
+mkTodoInput = do
+ component "TodoInput" \props -> React.do
+ value /\ setValue <- useState ""
+ React.pure $ R.form
+ { onSubmit: capture_ do
+ props.dispatch $ CreateTodo value
+ setValue $ const ""
+ , children:
+ [ R.input
+ { value
+ , onChange: capture targetValue $ traverse_ (setValue <<< const)
+ , style: R.css { lineHeight: "32px", width: "100%", boxSizing: "border-box" }
+ , placeholder: "Enter a task"
+ }
+ ]
+ , style: R.css { marginBottom: "16px", width: "100%" }
+ }
+
+mkTodoRow :: CreateComponent { id :: Int, todo :: Todo, dispatch :: Action -> Effect Unit } Unit
+mkTodoRow = component "Todo" \props -> React.do
+ React.pure $ R.div
+ { children:
+ [ R.label
+ { children:
+ [ R.input
+ { type: "checkbox"
+ , checked: props.todo.isComplete
+ , onChange: Events.handler_ $ props.dispatch $ ToggleTodo props.id
+ , tabIndex: "0"
+ }
+ , R.text props.todo.task
+ ]
+ , style: R.css { lineHeight: "32px", fontSize: "24px", flex: "1 0 auto" }
+ }
+ , R.a
+ { children: [ R.text "❌" ]
+ , onClick: capture_ $ props.dispatch $ DeleteTodo props.id
+ , style: R.css { cursor: "pointer" }
+ }
+ ]
+ , style: R.css
+ { display: "flex"
+ , flexFlow: "row"
+ , alignItems: "center"
+ }
+ }
+
+mkTodoFilters :: CreateComponent { filter :: TodoFilter, dispatch :: Action -> Effect Unit } Unit
+mkTodoFilters = component "TodoFilters" \props -> React.do
+ let
+ filterLink f label = R.a
+ { children: [ R.text label ]
+ , onClick: capture_ $ props.dispatch $ SetFilter f
+ , style: if props.filter == f
+ then R.css { cursor: "pointer", fontWeight: "bold" }
+ else R.css { cursor: "pointer" }
+ }
+ React.pure $ R.div
+ { children:
+ [ R.hr { style: R.css { color: "lightgrey" } }
+ , filterLink All "All"
+ , R.text " / "
+ , filterLink Complete "Complete"
+ , R.text " / "
+ , filterLink Incomplete "Incomplete"
+ ]
+ , style: R.css { marginTop: "16px" }
+ }
diff --git a/generated-docs/React/Basic.md b/generated-docs/React/Basic.md
index 2832ee1..3a3a8ac 100644
--- a/generated-docs/React/Basic.md
+++ b/generated-docs/React/Basic.md
@@ -1,354 +1,227 @@
## Module React.Basic
-#### `ComponentSpec`
+#### `Component`
``` purescript
-type ComponentSpec props state initialState action = { initialState :: initialState, update :: Self props state action -> action -> StateUpdate props state action, render :: Self props state action -> JSX, shouldUpdate :: Self props state action -> props -> state -> Boolean, didMount :: Self props state action -> Effect Unit, didUpdate :: Self props state action -> Effect Unit, willUnmount :: Self props state action -> Effect Unit, "$$type" :: ComponentType props state action }
+newtype Component props hooks
```
-`ComponentSpec` represents a React-Basic component implementation.
-
-These are the properties your component definition may override
-with specific implementations. None are required to be overridden, unless
-an overridden function interacts with `state`, in which case `initialState`
-is required (the compiler enforces this). While you _can_ use `state` and
-dispatch actions without defining `update`, doing so doesn't make much sense
-so the default `update` implementation will emit a warning.
-
-- `initialState`
- - The component's starting state.
- - Avoid mirroring prop values in state.
-- `update`
- - All state updates go through `update`.
- - `update` is called when `send` is used to dispatch an action.
- - State changes are described using `StateUpdate`. Only `Update` and `UpdateAndSideEffects` will cause rerenders and a call to `didUpdate`.
- - Side effects requested are only invoked _after_ any corrosponding state update has completed its render cycle and the changes have been applied. This means it is safe to interact with the DOM in a side effect, for example.
-- `render`
- - Takes a current snapshot of the component (`Self`) and converts it to renderable `JSX`.
-- `shouldUpdate`
- - Can be useful for occasional performance optimizations. Rarely necessary.
-- `didMount`
- - The React component's `componentDidMount` lifecycle. Useful for initiating an action on first mount, such as fetching data from a server.
-- `didUpdate`
- - The React component's `componentDidUpdate` lifecycle. Rarely necessary.
-- `willUnmount`
- - The React component's `componentWillUpdate` lifecycle. Any subscriptions or timers created in `didMount` or `didUpdate` should be disposed of here.
-
-The component spec is generally not exported from your component
-module and this type is rarely used explicitly. The simplified alias
-`Component` is usually sufficient, and `make` will validate whether
-your component's types line up.
+A React component
-For example:
+#### `CreateComponent`
-```purs
-component :: Component
-component = createComponent "Counter"
-
-type Props =
- { label :: String
- }
-
-data Action
- = Increment
+``` purescript
+type CreateComponent props hooks = Effect (Component props hooks)
+```
-counter :: Props -> JSX
-counter = make component
- { initialState = { counter: 0 }
+Alias for convenience. Creating components is effectful because
+React uses the function instance as the component's "identity"
+or "type".
- , update = \self action -> case action of
- Increment ->
- Update self.state { counter = self.state.counter + 1 }
+#### `JSX`
- , render = \self ->
- R.button
- { onClick: capture_ self Increment
- , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ]
- }
- }
+``` purescript
+data JSX :: Type
```
-This example component overrides `initialState`, `update`, and `render`.
-
-__*Note:* A `ComponentSpec` is *not* a valid React component by itself. If you would like to use
- a React-Basic component from JavaScript, use `toReactComponent`.__
+Represents rendered React VDOM (the result of calling `React.createElement`
+in JavaScript).
-__*Note:* `$$type` is for internal use only. It needs to be on the type to
- preserve its existence during a record update, as in the example above.__
+`JSX` is a `Monoid`:
-__*See also:* `Component`, `ComponentSpec`, `make`, `makeStateless`__
+- `append`
+ - Merge two `JSX` nodes using `React.Fragment`.
+- `mempty`
+ - The `empty` node; renders nothing.
-#### `createComponent`
+__*Hint:* Many useful utility functions already exist for Monoids. For example,
+ `guard` can be used to conditionally render a subtree of components.__
+##### Instances
``` purescript
-createComponent :: forall props state action. String -> ComponentSpec props state Unit action
+Semigroup JSX
+Monoid JSX
```
-Creates a `ComponentSpec` with a given Display Name.
-
-The resulting component spec is usually given the simplified `Component` type:
+#### `component`
-```purs
-component :: Component
-component = createComponent "Counter"
+``` purescript
+component :: forall hooks props. String -> (props -> Render Unit hooks JSX) -> CreateComponent props hooks
```
-This function should be used at the module level and considered side effecting.
-This is because React uses referential equality when deciding whether a new
-`JSX` tree is a valid update, or if it needs to be replaced entirely
-(expensive and clears component state lower in the tree).
-
-__*Note:* A `Component` is *not* a valid React component by itself. If you would like to use
- a React-Basic component from JavaScript, use `toReactComponent`.__
-
-__*See also:* `Component`, `ComponentSpec`, `make`, `makeStateless`__
+Create a React component given a display name and render function.
-#### `Component`
+#### `UseState`
``` purescript
-type Component = forall props state action. ComponentSpec props state Unit action
+data UseState :: Type -> Type -> Type
```
-A simplified alias for `ComponentSpec`. This type is usually used to represent
-the default component type returned from `createComponent`.
-
-#### `ComponentType`
+#### `useState`
``` purescript
-data ComponentType props state action
+useState :: forall hooks state. state -> Render hooks (UseState state hooks) (Tuple state ((state -> state) -> Effect Unit))
```
-Opaque component information for internal use.
-
-__*For the curious:* This is the "class" React will use to render and
- identify the component. It receives the `ComponentSpec` as a prop and knows
- how to defer behavior to it. It requires very specific props and is not useful by
- itself from JavaScript. For JavaScript interop, see `toReactComponent`.__
-
-#### `StateUpdate`
+#### `UseEffect`
``` purescript
-data StateUpdate props state action
- = NoUpdate
- | Update state
- | SideEffects (Self props state action -> Effect Unit)
- | UpdateAndSideEffects state (Self props state action -> Effect Unit)
+data UseEffect :: Type -> Type
```
-Used by the `update` function to describe the kind of state update and/or side
-effects desired.
-
-__*See also:* `ComponentSpec`__
-
-#### `Self`
+#### `useEffect`
``` purescript
-type Self props state action = { props :: props, state :: state, instance_ :: ReactComponentInstance }
+useEffect :: forall hooks. Array Key -> Effect (Effect Unit) -> Render hooks (UseEffect hooks) Unit
```
-`Self` represents the component instance at a particular point in time.
-
-- `props`
- - A snapshot of `props` taken when this `Self` was created.
-- `state`
- - A snapshot of `state` taken when this `Self` was created.
-- `instance_`
- - Unsafe escape hatch to the underlying component instance (`this` in the JavaScript React paradigm). Avoid as much as possible, but it's still frequently better than rewriting an entire component in JavaScript.
-
-__*See also:* `ComponentSpec`, `send`, `capture`, `readProps`, `readState`__
-
-#### `send`
+#### `UseReducer`
``` purescript
-send :: forall props state action. Self props state action -> action -> Effect Unit
+data UseReducer :: Type -> Type -> Type -> Type
```
-Dispatch an `action` into the component to be handled by `update`.
-
-__*See also:* `update`, `capture`__
-
-#### `sendAsync`
+#### `useReducer`
``` purescript
-sendAsync :: forall props state action. Self props state action -> Aff action -> Effect Unit
+useReducer :: forall hooks state action. ToKey state => state -> (state -> action -> state) -> Render hooks (UseReducer state action hooks) (Tuple state (action -> Effect Unit))
```
-Convenience function for sending an action when an `Aff` completes.
-
-__*Note:* Potential failure should be handled in the given `Aff` and converted
- to an action, as the default error handler will simply log the error to
- the console.__
-
-__*See also:* `send`__
-
-#### `capture`
+#### `UseRef`
``` purescript
-capture :: forall props state action a. Self props state action -> EventFn SyntheticEvent a -> (a -> action) -> EventHandler
+data UseRef :: Type -> Type -> Type
```
-Create a capturing\* `EventHandler` to send an action when an event occurs. For
-more complicated event handlers requiring `Effect`, use `handler` from `React.Basic.Events`.
-
-__\*calls `preventDefault` and `stopPropagation`__
-
-__*See also:* `update`, `capture_`, `monitor`, `React.Basic.Events`__
-
-#### `capture_`
+#### `Ref`
``` purescript
-capture_ :: forall props state action. Self props state action -> action -> EventHandler
+data Ref :: Type -> Type
```
-Like `capture`, but for actions which don't need to extract information from the Event.
-
-__*See also:* `update`, `capture`, `monitor_`__
-
-#### `monitor`
+#### `readRef`
``` purescript
-monitor :: forall props state action a. Self props state action -> EventFn SyntheticEvent a -> (a -> action) -> EventHandler
+readRef :: forall a. Ref a -> Effect a
```
-Like `capture`, but does not cancel the event.
-
-__*See also:* `update`, `capture`, `monitor\_`__
-
-#### `monitor_`
+#### `readRefMaybe`
``` purescript
-monitor_ :: forall props state action. Self props state action -> action -> EventHandler
+readRefMaybe :: forall a. Ref (Nullable a) -> Effect (Maybe a)
```
-Like `capture_`, but does not cancel the event.
-
-__*See also:* `update`, `monitor`, `capture_`, `React.Basic.Events`__
-
-#### `readProps`
+#### `writeRef`
``` purescript
-readProps :: forall props state action. Self props state action -> Effect props
+writeRef :: forall a. Ref a -> a -> Effect Unit
```
-Read the most up to date `props` directly from the component instance
-associated with this `Self`.
-
-_Note: This function is for specific, asynchronous edge cases.
- Generally, the `props` snapshot on `Self` is sufficient.
-
-__*See also:* `Self`__
-
-#### `readState`
+#### `renderRef`
``` purescript
-readState :: forall props state action. Self props state action -> Effect state
+renderRef :: forall hooks a. Ref a -> Render hooks hooks a
```
-Read the most up to date `state` directly from the component instance
-associated with this `Self`.
-
-_Note: This function is for specific, asynchronous edge cases.
- Generally, the `state` snapshot on `Self` is sufficient.
-
-__*See also:* `Self`__
-
-#### `make`
+#### `renderRefMaybe`
``` purescript
-make :: forall props state action. ComponentSpec props state state action -> props -> JSX
+renderRefMaybe :: forall hooks a. Ref (Nullable a) -> Render hooks hooks (Maybe a)
```
-Turn a `Component` into a usable render function.
-This is where you will want to provide customized implementations:
+#### `useRef`
-```purs
-component :: Component
-component = createComponent "Counter"
+``` purescript
+useRef :: forall hooks a. a -> Render hooks (UseRef a hooks) (Ref a)
+```
-type Props =
- { label :: String
- }
+#### `Key`
-data Action
- = Increment
+``` purescript
+data Key
+```
-counter :: Props -> JSX
-counter = make component
- { initialState = { counter: 0 }
+Keys represent values React uses to check for changes.
+This is done using JavaScript's reference equality (`===`),
+so complicated types may want to implement `ToKey` so that
+it returns a primative like a `String`. A timestamp appended
+to a unique ID, for example. Less strict cases can implement
+`ToKey` using `unsafeToKey`, while some extreme cases may
+need a hashing or stringifying mechanism.
- , update = \self action -> case action of
- Increment ->
- Update self.state { counter = self.state.counter + 1 }
+#### `ToKey`
- , render = \self ->
- R.button
- { onClick: capture_ self Increment
- , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ]
- }
- }
+``` purescript
+class ToKey a where
+ toKey :: a -> Key
```
-__*See also:* `makeStateless`, `createComponent`, `Component`, `ComponentSpec`__
+##### Instances
+``` purescript
+ToKey String
+ToKey Int
+ToKey Number
+ToKey Boolean
+ToKey { | a }
+ToKey (Array a)
+ToKey (Nullable a)
+ToKey (Maybe a)
+```
-#### `makeStateless`
+#### `unsafeToKey`
``` purescript
-makeStateless :: forall props. ComponentSpec props Unit Unit Unit -> (props -> JSX) -> props -> JSX
+unsafeToKey :: forall a. a -> Key
```
-Makes stateless component definition slightly less verbose:
-
-```purs
-component :: Component
-component = createComponent "Xyz"
+#### `empty`
-myComponent :: Props -> JSX
-myComponent = makeStateless component \props -> JSX
+``` purescript
+empty :: JSX
```
-__*Note:* The only difference between a stateless React-Basic component and
- a plain `props -> JSX` function is the presense of the component name
- in React's dev tools and error stacks. It's just a conceptual boundary.
- If this isn't important simply write a `props -> JSX` function.__
+An empty `JSX` node. This is often useful when you would like to conditionally
+show something, but you don't want to (or can't) modify the `children` prop
+on the parent node.
-__*See also:* `make`, `createComponent`, `Component`, `ComponentSpec`__
+__*See also:* `JSX`, Monoid `guard`__
-#### `JSX`
+#### `Render`
``` purescript
-data JSX :: Type
+newtype Render x y a
```
-Represents rendered React VDOM (the result of calling `React.createElement`
-in JavaScript).
-
-`JSX` is a `Monoid`:
+Render represents the effects allowed within a React component's
+body, i.e. during "render". This includes hooks and ends with
+returning JSX (see `pure`), but does not allow arbitrary side
+effects.
-- `append`
- - Merge two `JSX` nodes using `React.Fragment`.
-- `mempty`
- - The `empty` node; renders nothing.
+##### Instances
+``` purescript
+IxFunctor Render
+IxApply Render
+IxBind Render
+IxApplicative Render
+```
-__*Hint:* Many useful utility functions already exist for Monoids. For example,
- `guard` can be used to conditionally render a subtree of components.__
+#### `bind`
-##### Instances
``` purescript
-Semigroup JSX
-Monoid JSX
+bind :: forall a b x y z m. IxBind m => m x y a -> (a -> m y z b) -> m x z b
```
-#### `empty`
+#### `discard`
``` purescript
-empty :: JSX
+discard :: forall a b x y z m. IxBind m => m x y a -> (a -> m y z b) -> m x z b
```
-An empty `JSX` node. This is often useful when you would like to conditionally
-show something, but you don't want to (or can't) modify the `children` prop
-on the parent node.
+#### `pure`
-__*See also:* `JSX`, Monoid `guard`__
+``` purescript
+pure :: forall a x m. IxApplicative m => a -> m x x a
+```
#### `keyed`
@@ -375,68 +248,105 @@ __*See also:* `JSX`__
#### `element`
``` purescript
-element :: forall props. ReactComponent { | props } -> { | props } -> JSX
+element :: forall hooks props. Component { | props } hooks -> { | props } -> JSX
```
-Create a `JSX` node from a `ReactComponent`, by providing the props.
+Create a `JSX` node from a `Component`, by providing the props.
This function is for non-React-Basic React components, such as those
imported from FFI.
-__*See also:* `ReactComponent`, `elementKeyed`__
+__*See also:* `Component`, `elementKeyed`__
#### `elementKeyed`
``` purescript
-elementKeyed :: forall props. ReactComponent { | props } -> { key :: String | props } -> JSX
+elementKeyed :: forall hooks props. Component { | props } hooks -> { key :: String | props } -> JSX
```
-Create a `JSX` node from a `ReactComponent`, by providing the props and a key.
-
-This function is for non-React-Basic React components, such as those
-imported from FFI.
+Create a `JSX` node from a `Component`, by providing the props and a key.
-__*See also:* `ReactComponent`, `element`, React's documentation regarding the special `key` prop__
+__*See also:* `Component`, `element`, React's documentation regarding the special `key` prop__
-#### `ReactComponent`
+#### `displayName`
``` purescript
-data ReactComponent props
+displayName :: forall hooks props. Component props hooks -> String
```
-Represents a traditional React component. Useful for JavaScript interop and
-FFI. For example:
+Retrieve the Display Name from a `Component`. Useful for debugging and improving
+error messages in logs.
+
+__*See also:* `component`__
-```purs
-foreign import ComponentRequiringJSHacks :: ReactComponent { someProp :: String }
-```
-__*See also:* `element`, `toReactComponent`__
+### Re-exported from Data.Tuple:
-#### `ReactComponentInstance`
+#### `Tuple`
``` purescript
-data ReactComponentInstance
+data Tuple a b
+ = Tuple a b
```
-An opaque representation of a React component's instance (`this` in the JavaScript
-React paradigm). It exists as an escape hatch to unsafe behavior. Use it with
-caution.
+A simple product type for wrapping a pair of component values.
-#### `toReactComponent`
+##### Instances
+``` purescript
+(Show a, Show b) => Show (Tuple a b)
+(Eq a, Eq b) => Eq (Tuple a b)
+(Eq a) => Eq1 (Tuple a)
+(Ord a, Ord b) => Ord (Tuple a b)
+(Ord a) => Ord1 (Tuple a)
+(Bounded a, Bounded b) => Bounded (Tuple a b)
+Semigroupoid Tuple
+(Semigroup a, Semigroup b) => Semigroup (Tuple a b)
+(Monoid a, Monoid b) => Monoid (Tuple a b)
+(Semiring a, Semiring b) => Semiring (Tuple a b)
+(Ring a, Ring b) => Ring (Tuple a b)
+(CommutativeRing a, CommutativeRing b) => CommutativeRing (Tuple a b)
+(HeytingAlgebra a, HeytingAlgebra b) => HeytingAlgebra (Tuple a b)
+(BooleanAlgebra a, BooleanAlgebra b) => BooleanAlgebra (Tuple a b)
+Functor (Tuple a)
+FunctorWithIndex Unit (Tuple a)
+Invariant (Tuple a)
+Bifunctor Tuple
+(Semigroup a) => Apply (Tuple a)
+Biapply Tuple
+(Monoid a) => Applicative (Tuple a)
+Biapplicative Tuple
+(Semigroup a) => Bind (Tuple a)
+(Monoid a) => Monad (Tuple a)
+Extend (Tuple a)
+Comonad (Tuple a)
+(Lazy a, Lazy b) => Lazy (Tuple a b)
+Foldable (Tuple a)
+Foldable1 (Tuple a)
+FoldableWithIndex Unit (Tuple a)
+Bifoldable Tuple
+Traversable (Tuple a)
+Traversable1 (Tuple a)
+TraversableWithIndex Unit (Tuple a)
+Bitraversable Tuple
+(TypeEquals a Unit) => Distributive (Tuple a)
+```
+
+### Re-exported from Data.Tuple.Nested:
+
+#### `tuple2`
``` purescript
-toReactComponent :: forall jsProps props state action. ({ | jsProps } -> props) -> ComponentSpec props state state action -> ReactComponent { | jsProps }
+tuple2 :: forall a b. a -> b -> Tuple2 a b
```
-Convert a React-Basic `ComponentSpec` to a JavaScript-friendly React component.
-This function should only be used for JS interop and not normal React-Basic usage.
+Given 2 values, creates a 2-tuple.
-__*Note:* Like `createComponent`, `toReactComponent` is side effecting in that
- it creates a "class" React will see as unique each time it's called. Lift
- any usage up to the module level, usage in `render` or any other function,
- and applying any type classes to the `props`.__
+#### `(/\)`
-__*See also:* `ReactComponent`__
+``` purescript
+infixr 6 Tuple as /\
+```
+Shorthand for constructing n-tuples as nested pairs.
+`a /\ b /\ c /\ d /\ unit` becomes `Tuple a (Tuple b (Tuple c (Tuple d unit)))`
diff --git a/generated-docs/React/Basic/Compat.md b/generated-docs/React/Basic/Compat.md
new file mode 100644
index 0000000..3f32360
--- /dev/null
+++ b/generated-docs/React/Basic/Compat.md
@@ -0,0 +1,129 @@
+## Module React.Basic.Compat
+
+#### `UseStatefulComponent`
+
+``` purescript
+type UseStatefulComponent state = UseEffect (UseState state Unit)
+```
+
+#### `component`
+
+``` purescript
+component :: forall props state. { displayName :: String, initialState :: { | state }, receiveProps :: { props :: { | props }, state :: { | state }, setState :: ({ | state } -> { | state }) -> Effect Unit } -> Effect Unit, render :: { props :: { | props }, state :: { | state }, setState :: ({ | state } -> { | state }) -> Effect Unit } -> JSX } -> Component { | props } (UseStatefulComponent { | state })
+```
+
+Supports a common subset of the v2 API to ease the upgrade process
+
+#### `stateless`
+
+``` purescript
+stateless :: forall props. { displayName :: String, render :: { | props } -> JSX } -> Component { | props } Unit
+```
+
+Supports a common subset of the v2 API to ease the upgrade process
+
+
+### Re-exported from React.Basic:
+
+#### `UseState`
+
+``` purescript
+data UseState :: Type -> Type -> Type
+```
+
+#### `UseEffect`
+
+``` purescript
+data UseEffect :: Type -> Type
+```
+
+#### `JSX`
+
+``` purescript
+data JSX :: Type
+```
+
+Represents rendered React VDOM (the result of calling `React.createElement`
+in JavaScript).
+
+`JSX` is a `Monoid`:
+
+- `append`
+ - Merge two `JSX` nodes using `React.Fragment`.
+- `mempty`
+ - The `empty` node; renders nothing.
+
+__*Hint:* Many useful utility functions already exist for Monoids. For example,
+ `guard` can be used to conditionally render a subtree of components.__
+
+##### Instances
+``` purescript
+Semigroup JSX
+Monoid JSX
+```
+
+#### `Component`
+
+``` purescript
+newtype Component props hooks
+```
+
+A React component
+
+#### `keyed`
+
+``` purescript
+keyed :: String -> JSX -> JSX
+```
+
+Apply a React key to a subtree. React-Basic usually hides React's warning about
+using `key` props on components in an Array, but keys are still important for
+any dynamic lists of child components.
+
+__*See also:* React's documentation regarding the special `key` prop__
+
+#### `fragment`
+
+``` purescript
+fragment :: Array JSX -> JSX
+```
+
+Render an Array of children without a wrapping component.
+
+__*See also:* `JSX`__
+
+#### `empty`
+
+``` purescript
+empty :: JSX
+```
+
+An empty `JSX` node. This is often useful when you would like to conditionally
+show something, but you don't want to (or can't) modify the `children` prop
+on the parent node.
+
+__*See also:* `JSX`, Monoid `guard`__
+
+#### `elementKeyed`
+
+``` purescript
+elementKeyed :: forall hooks props. Component { | props } hooks -> { key :: String | props } -> JSX
+```
+
+Create a `JSX` node from a `Component`, by providing the props and a key.
+
+__*See also:* `Component`, `element`, React's documentation regarding the special `key` prop__
+
+#### `element`
+
+``` purescript
+element :: forall hooks props. Component { | props } hooks -> { | props } -> JSX
+```
+
+Create a `JSX` node from a `Component`, by providing the props.
+
+This function is for non-React-Basic React components, such as those
+imported from FFI.
+
+__*See also:* `Component`, `elementKeyed`__
+
diff --git a/generated-docs/React/Basic/DOM.md b/generated-docs/React/Basic/DOM.md
index 7afcb7b..4a4fc47 100644
--- a/generated-docs/React/Basic/DOM.md
+++ b/generated-docs/React/Basic/DOM.md
@@ -71,18 +71,6 @@ if an app existed and was unmounted successfully.
Note: Relies on `ReactDOM.unmountComponentAtNode`
-#### `findDOMNode`
-
-``` purescript
-findDOMNode :: ReactComponentInstance -> Effect (Either Error Node)
-```
-
-Returns the current DOM node associated with the given
-instance, or an Error if no node was found or the given
-instance was not mounted.
-
-Note: Relies on `ReactDOM.findDOMNode`
-
#### `createPortal`
``` purescript
@@ -2178,7 +2166,7 @@ a :: forall attrs attrs_. Union attrs attrs_ (SharedProps Props_a) => { | attrs
#### `SharedProps`
``` purescript
-type SharedProps specific = (key :: String, about :: String, acceptCharset :: String, accessKey :: String, allowFullScreen :: Boolean, allowTransparency :: String, autoComplete :: String, autoFocus :: String, autoPlay :: Boolean, capture :: Boolean, cellPadding :: String, cellSpacing :: String, charSet :: String, classID :: String, className :: String, colSpan :: Number, contentEditable :: String, contextMenu :: String, crossOrigin :: String, datatype :: String, dateTime :: String, dir :: String, draggable :: String, encType :: String, formAction :: String, formEncType :: String, formMethod :: String, formNoValidate :: String, formTarget :: String, frameBorder :: String, hidden :: Boolean, hrefLang :: String, htmlFor :: String, httpEquiv :: String, icon :: String, id :: String, inlist :: String, inputMode :: String, is :: String, itemID :: String, itemProp :: String, itemRef :: String, itemScope :: Boolean, itemType :: String, keyParams :: String, keyType :: String, lang :: String, marginHeight :: String, marginWidth :: String, maxLength :: String, mediaGroup :: String, minLength :: String, noValidate :: String, prefix :: String, property :: String, radioGroup :: String, readOnly :: Boolean, resource :: String, role :: String, rowSpan :: Number, scoped :: Boolean, seamless :: Boolean, security :: String, spellCheck :: String, srcDoc :: String, srcLang :: String, srcSet :: String, style :: CSS, tabIndex :: String, title :: String, typeof :: String, unselectable :: String, useMap :: String, vocab :: String, wmode :: String, onBlur :: EventHandler, onClick :: EventHandler, onFocus :: EventHandler | specific)
+type SharedProps specific = (key :: String, ref :: Ref (Nullable Node), about :: String, acceptCharset :: String, accessKey :: String, allowFullScreen :: Boolean, allowTransparency :: String, autoComplete :: String, autoFocus :: String, autoPlay :: Boolean, capture :: Boolean, cellPadding :: String, cellSpacing :: String, charSet :: String, classID :: String, className :: String, colSpan :: Number, contentEditable :: String, contextMenu :: String, crossOrigin :: String, datatype :: String, dateTime :: String, dir :: String, draggable :: String, encType :: String, formAction :: String, formEncType :: String, formMethod :: String, formNoValidate :: String, formTarget :: String, frameBorder :: String, hidden :: Boolean, hrefLang :: String, htmlFor :: String, httpEquiv :: String, icon :: String, id :: String, inlist :: String, inputMode :: String, is :: String, itemID :: String, itemProp :: String, itemRef :: String, itemScope :: Boolean, itemType :: String, keyParams :: String, keyType :: String, lang :: String, marginHeight :: String, marginWidth :: String, maxLength :: String, mediaGroup :: String, minLength :: String, noValidate :: String, prefix :: String, property :: String, radioGroup :: String, readOnly :: Boolean, resource :: String, role :: String, rowSpan :: Number, scoped :: Boolean, seamless :: Boolean, security :: String, spellCheck :: String, srcDoc :: String, srcLang :: String, srcSet :: String, style :: CSS, tabIndex :: String, title :: String, typeof :: String, unselectable :: String, useMap :: String, vocab :: String, wmode :: String, onBlur :: EventHandler, onClick :: EventHandler, onFocus :: EventHandler | specific)
```
Standard props which are shared by all DOM elements.
@@ -2194,6 +2182,6 @@ An abstract type representing records of CSS attributes.
#### `unsafeCreateDOMComponent`
``` purescript
-unsafeCreateDOMComponent :: forall props. String -> ReactComponent props
+unsafeCreateDOMComponent :: forall props. String -> Component props Unit
```
diff --git a/generated-docs/React/Basic/DOM/Events.md b/generated-docs/React/Basic/DOM/Events.md
index 4b86a99..29f8cdb 100644
--- a/generated-docs/React/Basic/DOM/Events.md
+++ b/generated-docs/React/Basic/DOM/Events.md
@@ -2,6 +2,18 @@
This module defines safe DOM event function and property accessors.
+#### `capture`
+
+``` purescript
+capture :: forall a. EventFn SyntheticEvent a -> (a -> Effect Unit) -> EventHandler
+```
+
+#### `capture_`
+
+``` purescript
+capture_ :: Effect Unit -> EventHandler
+```
+
#### `bubbles`
``` purescript
diff --git a/generated-docs/React/Basic/DOM/Internal.md b/generated-docs/React/Basic/DOM/Internal.md
index 8594101..0ace6b5 100644
--- a/generated-docs/React/Basic/DOM/Internal.md
+++ b/generated-docs/React/Basic/DOM/Internal.md
@@ -11,7 +11,7 @@ An abstract type representing records of CSS attributes.
#### `SharedProps`
``` purescript
-type SharedProps specific = (key :: String, about :: String, acceptCharset :: String, accessKey :: String, allowFullScreen :: Boolean, allowTransparency :: String, autoComplete :: String, autoFocus :: String, autoPlay :: Boolean, capture :: Boolean, cellPadding :: String, cellSpacing :: String, charSet :: String, classID :: String, className :: String, colSpan :: Number, contentEditable :: String, contextMenu :: String, crossOrigin :: String, datatype :: String, dateTime :: String, dir :: String, draggable :: String, encType :: String, formAction :: String, formEncType :: String, formMethod :: String, formNoValidate :: String, formTarget :: String, frameBorder :: String, hidden :: Boolean, hrefLang :: String, htmlFor :: String, httpEquiv :: String, icon :: String, id :: String, inlist :: String, inputMode :: String, is :: String, itemID :: String, itemProp :: String, itemRef :: String, itemScope :: Boolean, itemType :: String, keyParams :: String, keyType :: String, lang :: String, marginHeight :: String, marginWidth :: String, maxLength :: String, mediaGroup :: String, minLength :: String, noValidate :: String, prefix :: String, property :: String, radioGroup :: String, readOnly :: Boolean, resource :: String, role :: String, rowSpan :: Number, scoped :: Boolean, seamless :: Boolean, security :: String, spellCheck :: String, srcDoc :: String, srcLang :: String, srcSet :: String, style :: CSS, tabIndex :: String, title :: String, typeof :: String, unselectable :: String, useMap :: String, vocab :: String, wmode :: String, onBlur :: EventHandler, onClick :: EventHandler, onFocus :: EventHandler | specific)
+type SharedProps specific = (key :: String, ref :: Ref (Nullable Node), about :: String, acceptCharset :: String, accessKey :: String, allowFullScreen :: Boolean, allowTransparency :: String, autoComplete :: String, autoFocus :: String, autoPlay :: Boolean, capture :: Boolean, cellPadding :: String, cellSpacing :: String, charSet :: String, classID :: String, className :: String, colSpan :: Number, contentEditable :: String, contextMenu :: String, crossOrigin :: String, datatype :: String, dateTime :: String, dir :: String, draggable :: String, encType :: String, formAction :: String, formEncType :: String, formMethod :: String, formNoValidate :: String, formTarget :: String, frameBorder :: String, hidden :: Boolean, hrefLang :: String, htmlFor :: String, httpEquiv :: String, icon :: String, id :: String, inlist :: String, inputMode :: String, is :: String, itemID :: String, itemProp :: String, itemRef :: String, itemScope :: Boolean, itemType :: String, keyParams :: String, keyType :: String, lang :: String, marginHeight :: String, marginWidth :: String, maxLength :: String, mediaGroup :: String, minLength :: String, noValidate :: String, prefix :: String, property :: String, radioGroup :: String, readOnly :: Boolean, resource :: String, role :: String, rowSpan :: Number, scoped :: Boolean, seamless :: Boolean, security :: String, spellCheck :: String, srcDoc :: String, srcLang :: String, srcSet :: String, style :: CSS, tabIndex :: String, title :: String, typeof :: String, unselectable :: String, useMap :: String, vocab :: String, wmode :: String, onBlur :: EventHandler, onClick :: EventHandler, onFocus :: EventHandler | specific)
```
Standard props which are shared by all DOM elements.
@@ -19,7 +19,7 @@ Standard props which are shared by all DOM elements.
#### `unsafeCreateDOMComponent`
``` purescript
-unsafeCreateDOMComponent :: forall props. String -> ReactComponent props
+unsafeCreateDOMComponent :: forall props. String -> Component props Unit
```
diff --git a/src/React/Basic.js b/src/React/Basic.js
index 2ffd725..3b56355 100644
--- a/src/React/Basic.js
+++ b/src/React/Basic.js
@@ -3,151 +3,48 @@
var React = require("react");
var Fragment = React.Fragment || "div";
-exports.createComponent = (function() {
- // Begin component prototype functions
- // (`this`-dependent, defined outside `createComponent`
- // for a slight performance boost)
- function toSelf() {
- var self = {
- props: this.props.$$props,
- state: this.state === null ? null : this.state.$$state,
- instance_: this
+exports.memo_ = React.memo;
+
+exports.useState_ = function(tuple, initialState) {
+ var r = React.useState(initialState);
+ var state = r[0];
+ var setState = r[1];
+ return tuple(state, function(update) {
+ return function() {
+ return setState(update);
};
- return self;
- }
-
- function shouldComponentUpdate(nextProps, nextState) {
- var shouldUpdate = this.$$spec.shouldUpdate;
- return shouldUpdate === undefined
- ? true
- : shouldUpdate(this.toSelf())(nextProps.$$props)(
- nextState === null ? null : nextState.$$state
- );
- }
-
- function componentDidMount() {
- var didMount = this.$$spec.didMount;
- if (didMount !== undefined) {
- didMount(this.toSelf())();
- }
- }
-
- function componentDidUpdate() {
- var didUpdate = this.$$spec.didUpdate;
- if (didUpdate !== undefined) {
- didUpdate(this.toSelf())();
- }
- }
-
- function componentWillUnmount() {
- this.$$mounted = false;
- var willUnmount = this.$$spec.willUnmount;
- if (willUnmount !== undefined) {
- willUnmount(this.toSelf())();
- }
- }
-
- function render() {
- return this.$$spec.render(this.toSelf());
- }
- // End component prototype functions
-
- return function(displayName) {
- var Component = function constructor(props) {
- this.$$mounted = true;
- this.$$spec = props.$$spec;
- this.state =
- // React may optimize components with no state,
- // so we leave state null if it was left as
- // the default value.
- this.$$spec.initialState === undefined
- ? null
- : { $$state: this.$$spec.initialState };
- return this;
- };
-
- Component.displayName = displayName;
- Component.prototype = Object.create(React.Component.prototype);
- Component.prototype.constructor = Component;
- Component.prototype.toSelf = toSelf;
- Component.prototype.shouldComponentUpdate = shouldComponentUpdate;
- Component.prototype.componentDidMount = componentDidMount;
- Component.prototype.componentDidUpdate = componentDidUpdate;
- Component.prototype.componentWillUnmount = componentWillUnmount;
- Component.prototype.render = render;
-
- return Component;
- };
-})();
-
-exports.send_ = function(buildStateUpdate) {
- return function(self, action) {
- if (!self.instance_.$$mounted) {
- exports.warningUnmountedComponentAction(self, action);
- return;
- }
- if (self.instance_.$$spec.update === undefined) {
- exports.warningDefaultUpdate(self, action);
- return;
- }
- var sideEffects = null;
- self.instance_.setState(
- function(s) {
- var setStateContext = self.instance_.toSelf();
- setStateContext.state = s.$$state;
- var updates = buildStateUpdate(
- self.instance_.$$spec.update(setStateContext)(action)
- );
- if (updates.effects !== null) {
- sideEffects = updates.effects;
- }
- if (updates.state !== null && updates.state !== s.$$state) {
- return { $$state: updates.state };
- } else {
- return null;
- }
- },
- function() {
- if (sideEffects !== null) {
- sideEffects(this.toSelf())();
- }
- }
- );
- };
+ });
};
-exports.readProps = function(self) {
- return self.instance_.props.$$props;
+exports.useEffect_ = React.useEffect;
+
+exports.useLayoutEffect_ = React.useLayoutEffect;
+
+exports.useReducer_ = function(tuple, reducer, initialState, initialAction) {
+ var r = React.useReducer(reducer, initialState, initialAction);
+ var state = r[0];
+ var dispatch = r[1];
+ return tuple(state, function(action) {
+ return function() {
+ return dispatch(action);
+ };
+ });
};
-exports.readState = function(self) {
- var state = self.instance_.state;
- return state === null ? null : state.$$state;
+exports.useRef_ = React.useRef;
+
+exports.readRef_ = function(ref) {
+ return ref.current;
};
-exports.make = function(_unionDict) {
- return function($$type) {
- return function($$spec) {
- var $$specPadded = {
- initialState: $$spec.initialState,
- update: $$spec.update,
- render: $$spec.render,
- shouldUpdate: $$spec.shouldUpdate,
- didMount: $$spec.didMount,
- didUpdate: $$spec.didUpdate,
- willUnmount: $$spec.willUnmount
- };
- return function($$props) {
- var props = {
- $$props: $$props,
- $$spec: $$specPadded
- };
- return React.createElement($$type, props);
- };
- };
- };
+exports.writeRef_ = function(ref, a) {
+ ref.current = a;
};
+// exports.useContext_ = React.useContext;
+
+exports.useMemo_ = React.useMemo;
+
exports.empty = null;
exports.keyed_ = function(key, child) {
@@ -167,75 +64,16 @@ exports.fragment = function(children) {
return React.createElement.apply(null, [Fragment, {}].concat(children));
};
-exports.displayNameFromComponent = function($$type) {
- return $$type.displayName || "[unknown]";
+exports.displayName = function(component) {
+ return typeof component === "string"
+ ? component
+ : component.displayName || "[unknown]";
};
-exports.displayNameFromSelf = function(self) {
- return exports.displayNameFromComponent(self.instance_.constructor);
-};
-
-exports.toReactComponent = function(_unionDict) {
- return function(fromJSProps) {
- return function($$type) {
- return function($$spec) {
- var $$specPadded = {
- initialState: $$spec.initialState,
- update: $$spec.update,
- render: $$spec.render,
- shouldUpdate: $$spec.shouldUpdate,
- didMount: $$spec.didMount,
- didUpdate: $$spec.didUpdate,
- willUnmount: $$spec.willUnmount
- };
-
- var Component = function constructor() {
- return this;
- };
-
- Component.prototype = Object.create(React.Component.prototype);
-
- Component.displayName = $$type.displayName + " (Wrapper)";
-
- Component.prototype.render = function() {
- var props = {
- $$props: fromJSProps(this.props),
- $$spec: $$specPadded
- };
- return React.createElement($$type, props);
- };
-
- return Component;
- };
- };
+exports.unsafeSetDisplayName = function(displayName, component) {
+ component.displayName = displayName;
+ component.toString = function() {
+ return displayName;
};
-};
-
-exports.warningDefaultUpdate = function(self, action) {
- console.error(
- "A " +
- exports.displayNameFromSelf(self) +
- " component received an action but has no `update` function defined. Override the default `update` function to handle this action."
- );
- console.error("Self:", self);
- console.error("Action:", action);
-};
-
-exports.warningUnmountedComponentAction = function(self, action) {
- console.error(
- "An unmounted " +
- exports.displayNameFromSelf(self) +
- " component received the action below. Actions received by unmounted components usually indicate a memory leak. Make sure to unsubscribe from any async work in `willUnmount`."
- );
- console.error("Self:", self);
- console.error("Action:", action);
-};
-
-exports.warningFailedAsyncAction = function(self, error) {
- console.error(
- "An async action failed in a " +
- exports.displayNameFromSelf(self) +
- " component."
- );
- console.error(error);
+ return component;
};
diff --git a/src/React/Basic.purs b/src/React/Basic.purs
index b6bae67..3a5438f 100644
--- a/src/React/Basic.purs
+++ b/src/React/Basic.purs
@@ -1,321 +1,212 @@
module React.Basic
- ( ComponentSpec
- , createComponent
- , Component
- , StateUpdate(..)
- , Self
- , send
- , sendAsync
- , capture
- , capture_
- , monitor
- , monitor_
- , readProps
- , readState
- , make
- , makeStateless
+ ( Component
+ , CreateComponent
, JSX
+ , component
+ , memo
+ , UseState
+ , useState
+ , UseEffect
+ , useEffect
+ , UseLayoutEffect
+ , useLayoutEffect
+ , UseReducer
+ , useReducer
+ , UseRef
+ , Ref
+ , readRef
+ , readRefMaybe
+ , writeRef
+ , renderRef
+ , renderRefMaybe
+ , useRef
+ -- , UseContext
+ -- , Context
+ -- , useContext
+ , UseMemo
+ , useMemo
+ , Key
+ , class ToKey
+ , toKey
+ , unsafeToKey
, empty
+ , Render
+ , bind
+ , discard
+ , pure
, keyed
, fragment
, element
, elementKeyed
- , displayNameFromComponent
- , displayNameFromSelf
- , ReactComponent
- , ReactComponentInstance
- , toReactComponent
+ , displayName
+ , module Data.Tuple
+ , module Data.Tuple.Nested
) where
-import Prelude
-
-import Data.Either (Either(..))
-import Data.Function.Uncurried (Fn1, Fn2, runFn1, runFn2)
-import Data.Nullable (Nullable, notNull, null)
+import Prelude hiding (bind,discard,pure)
+
+import Control.Applicative.Indexed (class IxApplicative, ipure)
+import Control.Apply.Indexed (class IxApply)
+import Control.Bind.Indexed (class IxBind, ibind)
+import Data.Function.Uncurried (Fn2, mkFn2, runFn2)
+import Data.Functor.Indexed (class IxFunctor)
+import Data.Maybe (Maybe)
+import Data.Nullable (Nullable, toMaybe, toNullable)
+import Data.Tuple (Tuple(..))
+import Data.Tuple.Nested (tuple2, (/\))
import Effect (Effect)
-import Effect.Aff (Aff, Error, runAff_)
-import Effect.Uncurried (EffectFn2, runEffectFn2)
-import React.Basic.DOM.Events (preventDefault, stopPropagation)
-import React.Basic.Events (EventFn, EventHandler, SyntheticEvent, handler)
-import Type.Row (class Union)
+import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, mkEffectFn1, runEffectFn1, runEffectFn2, runEffectFn3)
+import Prelude (bind, pure) as Prelude
+import Unsafe.Coerce (unsafeCoerce)
--- | `ComponentSpec` represents a React-Basic component implementation.
--- |
--- | These are the properties your component definition may override
--- | with specific implementations. None are required to be overridden, unless
--- | an overridden function interacts with `state`, in which case `initialState`
--- | is required (the compiler enforces this). While you _can_ use `state` and
--- | dispatch actions without defining `update`, doing so doesn't make much sense
--- | so the default `update` implementation will emit a warning.
--- |
--- | - `initialState`
--- | - The component's starting state.
--- | - Avoid mirroring prop values in state.
--- | - `update`
--- | - All state updates go through `update`.
--- | - `update` is called when `send` is used to dispatch an action.
--- | - State changes are described using `StateUpdate`. Only `Update` and `UpdateAndSideEffects` will cause rerenders and a call to `didUpdate`.
--- | - Side effects requested are only invoked _after_ any corrosponding state update has completed its render cycle and the changes have been applied. This means it is safe to interact with the DOM in a side effect, for example.
--- | - `render`
--- | - Takes a current snapshot of the component (`Self`) and converts it to renderable `JSX`.
--- | - `shouldUpdate`
--- | - Can be useful for occasional performance optimizations. Rarely necessary.
--- | - `didMount`
--- | - The React component's `componentDidMount` lifecycle. Useful for initiating an action on first mount, such as fetching data from a server.
--- | - `didUpdate`
--- | - The React component's `componentDidUpdate` lifecycle. Rarely necessary.
--- | - `willUnmount`
--- | - The React component's `componentWillUpdate` lifecycle. Any subscriptions or timers created in `didMount` or `didUpdate` should be disposed of here.
--- |
--- | The component spec is generally not exported from your component
--- | module and this type is rarely used explicitly. The simplified alias
--- | `Component` is usually sufficient, and `make` will validate whether
--- | your component's types line up.
--- |
--- | For example:
--- |
--- | ```purs
--- | component :: Component Props
--- | component = createComponent "Counter"
--- |
--- | type Props =
--- | { label :: String
--- | }
--- |
--- | data Action
--- | = Increment
--- |
--- | counter :: Props -> JSX
--- | counter = make component
--- | { initialState = { counter: 0 }
--- |
--- | , update = \self action -> case action of
--- | Increment ->
--- | Update self.state { counter = self.state.counter + 1 }
--- |
--- | , render = \self ->
--- | R.button
--- | { onClick: capture_ self Increment
--- | , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ]
--- | }
--- | }
--- | ```
--- |
--- | This example component overrides `initialState`, `update`, and `render`.
--- |
--- | __*Note:* A `ComponentSpec` is *not* a valid React component by itself. If you would like to use
--- | a React-Basic component from JavaScript, use `toReactComponent`.__
--- |
--- | __*See also:* `Component`, `ComponentSpec`, `make`, `makeStateless`__
-type ComponentSpec props state action =
- ( initialState :: state
- , update :: Self props state action -> action -> StateUpdate props state action
- , render :: Self props state action -> JSX
- , shouldUpdate :: Self props state action -> props -> state -> Boolean
- , didMount :: Self props state action -> Effect Unit
- , didUpdate :: Self props state action -> Effect Unit
- , willUnmount :: Self props state action -> Effect Unit
- )
-
--- | Creates a `ComponentSpec` with a given Display Name.
--- |
--- | The resulting component spec is usually given the simplified `Component` type:
--- |
--- | ```purs
--- | component :: Component Props
--- | component = createComponent "Counter"
--- | ```
--- |
--- | This function should be used at the module level and considered side effecting.
--- | This is because React uses referential equality when deciding whether a new
--- | `JSX` tree is a valid update, or if it needs to be replaced entirely
--- | (expensive and clears component state lower in the tree).
--- |
--- | __*Note:* A specific type for the props in `Component props` should always be chosen at this point.
--- | It's technically possible to declare the component with `forall props. Component props`
--- | but doing so is unsafe. Leaving the prop type open allows the use of a single `Component`
--- | definition in multiple React-Basic components that may have different prop types. Because
--- | component lifecycles are managed by React, it's possible for incompatible prop values to
--- | be passed into a lifecycle function.__
--- |
--- | __*Note:* A `Component` is *not* a valid React component by itself. If you would like to use
--- | a React-Basic component from JavaScript, use `toReactComponent`.__
--- |
--- | __*See also:* `Component`, `ComponentSpec`, `make`, `makeStateless`__
-foreign import createComponent
- :: forall props
+-- | A React component
+newtype Component props hooks = Component (EffectFn1 props JSX)
+
+-- | Alias for convenience. Creating components is effectful because
+-- | React uses the function instance as the component's "identity"
+-- | or "type".
+type CreateComponent props hooks = Effect (Component props hooks)
+
+-- | Create a React component given a display name and render function.
+component
+ :: forall hooks props
. String
- -> Component props
+ -> (props -> Render Unit hooks JSX)
+ -> CreateComponent props hooks
+component name renderFn =
+ let c = Component (mkEffectFn1 (\props -> case renderFn props of Render a -> a))
+ in runEffectFn2 unsafeSetDisplayName name c
+
+memo
+ :: forall hooks props
+ . CreateComponent hooks props
+ -> CreateComponent hooks props
+memo = flip Prelude.bind (runEffectFn1 memo_)
+
+foreign import data UseState :: Type -> Type -> Type
+
+useState
+ :: forall hooks state
+ . state
+ -> Render hooks (UseState state hooks) (Tuple state ((state -> state) -> Effect Unit))
+useState initialState = Render do
+ runEffectFn2 useState_ (mkFn2 Tuple) initialState
+
+foreign import data UseEffect :: Type -> Type
+
+useEffect
+ :: forall hooks
+ . Array Key
+ -> Effect (Effect Unit)
+ -> Render hooks (UseEffect hooks) Unit
+useEffect keys effect = Render (runEffectFn2 useEffect_ effect keys)
+
+foreign import data UseLayoutEffect :: Type -> Type
+
+useLayoutEffect
+ :: forall hooks
+ . Array Key
+ -> Effect (Effect Unit)
+ -> Render hooks (UseLayoutEffect hooks) Unit
+useLayoutEffect keys effect = Render (runEffectFn2 useLayoutEffect_ effect keys)
--- | A simplified alias for `ComponentSpec`. This type is usually used to represent
--- | the default component type returned from `createComponent`.
--- type Component props = forall state action. ComponentSpec props state action
+foreign import data UseReducer :: Type -> Type -> Type -> Type
--- | Opaque component information for internal use.
--- |
--- | __*For the curious:* This is the "class" React will use to render and
--- | identify the component. It receives the `ComponentSpec` as a prop and knows
--- | how to defer behavior to it. It requires very specific props and is not useful by
--- | itself from JavaScript. For JavaScript interop, see `toReactComponent`.__
-data Component props
-
--- | Used by the `update` function to describe the kind of state update and/or side
--- | effects desired.
--- |
--- | __*See also:* `ComponentSpec`__
-data StateUpdate props state action
- = NoUpdate
- | Update state
- | SideEffects (Self props state action -> Effect Unit)
- | UpdateAndSideEffects state (Self props state action -> Effect Unit)
-
--- | `Self` represents the component instance at a particular point in time.
--- |
--- | - `props`
--- | - A snapshot of `props` taken when this `Self` was created.
--- | - `state`
--- | - A snapshot of `state` taken when this `Self` was created.
--- | - `instance_`
--- | - Unsafe escape hatch to the underlying component instance (`this` in the JavaScript React paradigm). Avoid as much as possible, but it's still frequently better than rewriting an entire component in JavaScript.
--- |
--- | __*See also:* `ComponentSpec`, `send`, `capture`, `readProps`, `readState`__
-type Self props state action =
- { props :: props
- , state :: state
- , instance_ :: ReactComponentInstance
- }
-
--- | Dispatch an `action` into the component to be handled by `update`.
--- |
--- | __*See also:* `update`, `capture`__
-send :: forall props state action. Self props state action -> action -> Effect Unit
-send = runEffectFn2 (runFn1 send_ buildStateUpdate)
+useReducer
+ :: forall hooks state action
+ . ToKey state
+ => state
+ -> (state -> action -> state)
+ -> Render hooks (UseReducer state action hooks) (Tuple state (action -> Effect Unit))
+useReducer initialState reducer = Render do
+ runEffectFn3 useReducer_
+ (mkFn2 Tuple)
+ (mkFn2 reducer)
+ initialState
--- | Convenience function for sending an action when an `Aff` completes.
--- |
--- | __*Note:* Potential failure should be handled in the given `Aff` and converted
--- | to an action, as the default error handler will simply log the error to
--- | the console.__
--- |
--- | __*See also:* `send`__
-sendAsync
- :: forall props state action
- . Self props state action
- -> Aff action
- -> Effect Unit
-sendAsync self work = runAff_ handle work
- where
- handle (Right action) = send self action
- handle (Left err) = runEffectFn2 warningFailedAsyncAction self err
-
--- | Create a capturing\* `EventHandler` to send an action when an event occurs. For
--- | more complicated event handlers requiring `Effect`, use `handler` from `React.Basic.Events`.
--- |
--- | __\*calls `preventDefault` and `stopPropagation`__
--- |
--- | __*See also:* `update`, `capture_`, `monitor`, `React.Basic.Events`__
-capture :: forall props state action a. Self props state action -> EventFn SyntheticEvent a -> (a -> action) -> EventHandler
-capture self eventFn = monitor self (preventDefault >>> stopPropagation >>> eventFn)
+foreign import data UseRef :: Type -> Type -> Type
--- | Like `capture`, but for actions which don't need to extract information from the Event.
--- |
--- | __*See also:* `update`, `capture`, `monitor_`__
-capture_ :: forall props state action. Self props state action -> action -> EventHandler
-capture_ self action = capture self identity \_ -> action
+foreign import data Ref :: Type -> Type
--- | Like `capture`, but does not cancel the event.
--- |
--- | __*See also:* `update`, `capture`, `monitor\_`__
-monitor :: forall props state action a. Self props state action -> EventFn SyntheticEvent a -> (a -> action) -> EventHandler
-monitor self eventFn makeAction = handler eventFn \a -> send self (makeAction a)
+useRef
+ :: forall hooks a
+ . a
+ -> Render hooks (UseRef a hooks) (Ref a)
+useRef initialValue = Render do
+ runEffectFn1 useRef_ initialValue
--- | Like `capture_`, but does not cancel the event.
--- |
--- | __*See also:* `update`, `monitor`, `capture_`, `React.Basic.Events`__
-monitor_ :: forall props state action. Self props state action -> action -> EventHandler
-monitor_ self action = monitor self identity \_ -> action
+readRef :: forall a. Ref a -> Effect a
+readRef = runEffectFn1 readRef_
--- | Read the most up to date `props` directly from the component instance
--- | associated with this `Self`.
--- |
--- | _Note: This function is for specific, asynchronous edge cases.
--- | Generally, the `props` snapshot on `Self` is sufficient.
--- |
--- | __*See also:* `Self`__
-foreign import readProps :: forall props state action. Self props state action -> Effect props
+readRefMaybe :: forall a. Ref (Nullable a) -> Effect (Maybe a)
+readRefMaybe a = map toMaybe (readRef a)
--- | Read the most up to date `state` directly from the component instance
--- | associated with this `Self`.
--- |
--- | _Note: This function is for specific, asynchronous edge cases.
--- | Generally, the `state` snapshot on `Self` is sufficient.
--- |
--- | __*See also:* `Self`__
-foreign import readState :: forall props state action. Self props state action -> Effect state
+writeRef :: forall a. Ref a -> a -> Effect Unit
+writeRef = runEffectFn2 writeRef_
--- | Turn a `Component` into a usable render function.
--- | This is where you will want to provide customized implementations:
--- |
--- | ```purs
--- | component :: Component Props
--- | component = createComponent "Counter"
--- |
--- | type Props =
--- | { label :: String
--- | }
--- |
--- | data Action
--- | = Increment
--- |
--- | counter :: Props -> JSX
--- | counter = make component
--- | { initialState = { counter: 0 }
--- |
--- | , update = \self action -> case action of
--- | Increment ->
--- | Update self.state { counter = self.state.counter + 1 }
--- |
--- | , render = \self ->
--- | R.button
--- | { onClick: capture_ self Increment
--- | , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ]
--- | }
--- | }
--- | ```
--- |
--- | __*See also:* `makeStateless`, `createComponent`, `Component`, `ComponentSpec`__
-foreign import make
- :: forall spec spec_ props state action
- . Union spec spec_ (ComponentSpec props state action)
- => Component props
- -> { render :: Self props state action -> JSX | spec }
- -> props
- -> JSX
+renderRef :: forall hooks a. Ref a -> Render hooks hooks a
+renderRef ref = Render (readRef ref)
--- | Makes stateless component definition slightly less verbose:
--- |
--- | ```purs
--- | component :: Component Props
--- | component = createComponent "Xyz"
--- |
--- | myComponent :: Props -> JSX
--- | myComponent = makeStateless component \props -> JSX
--- | ```
--- |
--- | __*Note:* The only difference between a stateless React-Basic component and
--- | a plain `props -> JSX` function is the presense of the component name
--- | in React's dev tools and error stacks. It's just a conceptual boundary.
--- | If this isn't important simply write a `props -> JSX` function.__
--- |
--- | __*See also:* `make`, `createComponent`, `Component`, `ComponentSpec`__
-makeStateless
- :: forall props
- . Component props
- -> (props -> JSX)
- -> props
- -> JSX
-makeStateless component render =
- make component { render: \self -> render self.props }
+renderRefMaybe :: forall hooks a. Ref (Nullable a) -> Render hooks hooks (Maybe a)
+renderRefMaybe a = Render (readRefMaybe a)
+
+-- foreign import data UseContext :: Type -> Type -> Type
+
+-- foreign import data Context :: Type -> Type
+
+-- useContext
+-- :: forall hooks a
+-- . Context a
+-- -> Render hooks (UseContext a hooks) a
+-- useContext context = Render (runEffectFn1 useContext_ context)
+
+foreign import data UseMemo :: Type -> Type -> Type
+
+useMemo
+ :: forall hooks a
+ . Array Key
+ -> (Unit -> a)
+ -> Render hooks (UseMemo a hooks) a
+useMemo keys factory = Render (runEffectFn2 useMemo_ factory keys)
+
+-- | Keys represent values React uses to check for changes.
+-- | This is done using JavaScript's reference equality (`===`),
+-- | so complicated types may want to implement `ToKey` so that
+-- | it returns a primative like a `String`. A timestamp appended
+-- | to a unique ID, for example. Less strict cases can implement
+-- | `ToKey` using `unsafeToKey`, while some extreme cases may
+-- | need a hashing or stringifying mechanism.
+data Key
+
+class ToKey a where
+ toKey :: a -> Key
+
+unsafeToKey :: forall a. a -> Key
+unsafeToKey = unsafeCoerce
+
+instance trString :: ToKey String where
+ toKey = unsafeToKey
+
+instance trInt :: ToKey Int where
+ toKey = unsafeToKey
+
+instance trNumber :: ToKey Number where
+ toKey = unsafeToKey
+
+instance trBoolean :: ToKey Boolean where
+ toKey = unsafeToKey
+
+instance trRecord :: ToKey (Record a) where
+ toKey = unsafeToKey
+
+instance trArray :: ToKey (Array a) where
+ toKey = unsafeToKey
+
+instance trNullable :: ToKey (Nullable a) where
+ toKey = unsafeToKey
+
+instance trMaybe :: ToKey (Maybe a) where
+ toKey a = toKey (toNullable a)
-- | Represents rendered React VDOM (the result of calling `React.createElement`
-- | in JavaScript).
@@ -344,6 +235,33 @@ instance monoidJSX :: Monoid JSX where
-- | __*See also:* `JSX`, Monoid `guard`__
foreign import empty :: JSX
+-- | Render represents the effects allowed within a React component's
+-- | body, i.e. during "render". This includes hooks and ends with
+-- | returning JSX (see `pure`), but does not allow arbitrary side
+-- | effects.
+newtype Render x y a = Render (Effect a)
+
+instance ixFunctorRender :: IxFunctor Render where
+ imap f (Render a) = Render (map f a)
+
+instance ixApplyRender :: IxApply Render where
+ iapply (Render f) (Render a) = Render (apply f a)
+
+instance ixBindRender :: IxBind Render where
+ ibind (Render m) f = Render (Prelude.bind m \a -> case f a of Render b -> b)
+
+instance ixApplicativeRender :: IxApplicative Render where
+ ipure a = Render (Prelude.pure a)
+
+bind :: forall a b x y z m. IxBind m => m x y a -> (a -> m y z b) -> m x z b
+bind = ibind
+
+discard :: forall a b x y z m. IxBind m => m x y a -> (a -> m y z b) -> m x z b
+discard = ibind
+
+pure :: forall a x m. IxApplicative m => a -> m x x a
+pure = ipure
+
-- | Apply a React key to a subtree. React-Basic usually hides React's warning about
-- | using `key` props on components in an Array, but keys are still important for
-- | any dynamic lists of child components.
@@ -357,150 +275,119 @@ keyed = runFn2 keyed_
-- | __*See also:* `JSX`__
foreign import fragment :: Array JSX -> JSX
--- | Create a `JSX` node from a `ReactComponent`, by providing the props.
+-- | Create a `JSX` node from a `Component`, by providing the props.
-- |
-- | This function is for non-React-Basic React components, such as those
-- | imported from FFI.
-- |
--- | __*See also:* `ReactComponent`, `elementKeyed`__
+-- | __*See also:* `Component`, `elementKeyed`__
element
- :: forall props
- . ReactComponent { | props }
- -> { | props }
+ :: forall hooks props
+ . Component {| props } hooks
+ -> {| props }
-> JSX
-element = runFn2 element_
+element (Component c) props = runFn2 element_ c props
--- | Create a `JSX` node from a `ReactComponent`, by providing the props and a key.
--- |
--- | This function is for non-React-Basic React components, such as those
--- | imported from FFI.
+-- | Create a `JSX` node from a `Component`, by providing the props and a key.
-- |
--- | __*See also:* `ReactComponent`, `element`, React's documentation regarding the special `key` prop__
+-- | __*See also:* `Component`, `element`, React's documentation regarding the special `key` prop__
elementKeyed
- :: forall props
- . ReactComponent { | props }
+ :: forall hooks props
+ . Component {| props } hooks
-> { key :: String | props }
-> JSX
elementKeyed = runFn2 elementKeyed_
--- | Retrieve the Display Name from a `ComponentSpec`. Useful for debugging and improving
--- | error messages in logs.
--- |
--- | __*See also:* `displayNameFromSelf`, `createComponent`__
-foreign import displayNameFromComponent
- :: forall props
- . Component props
- -> String
-
--- | Retrieve the Display Name from a `Self`. Useful for debugging and improving
+-- | Retrieve the Display Name from a `Component`. Useful for debugging and improving
-- | error messages in logs.
-- |
--- | __*See also:* `displayNameFromComponent`, `createComponent`__
-foreign import displayNameFromSelf
- :: forall props state action
- . Self props state action
+-- | __*See also:* `component`__
+foreign import displayName
+ :: forall hooks props
+ . Component props hooks
-> String
--- | Represents a traditional React component. Useful for JavaScript interop and
--- | FFI. For example:
--- |
--- | ```purs
--- | foreign import ComponentRequiringJSHacks :: ReactComponent { someProp :: String }
--- | ```
--- |
--- | __*See also:* `element`, `toReactComponent`__
-data ReactComponent props
-
--- | An opaque representation of a React component's instance (`this` in the JavaScript
--- | React paradigm). It exists as an escape hatch to unsafe behavior. Use it with
--- | caution.
-data ReactComponentInstance
-
--- | Convert a React-Basic `ComponentSpec` to a JavaScript-friendly React component.
--- | This function should only be used for JS interop and not normal React-Basic usage.
--- |
--- | __*Note:* Like `createComponent`, `toReactComponent` is side effecting in that
--- | it creates a "class" React will see as unique each time it's called. Lift
--- | any usage up to the module level, usage in `render` or any other function,
--- | and applying any type classes to the `props`.__
--- |
--- | __*See also:* `ReactComponent`__
-foreign import toReactComponent
- :: forall spec spec_ jsProps props state action
- . Union spec spec_ (ComponentSpec props state action)
- => ({ | jsProps } -> props)
- -> Component props
- -> { render :: Self props state action -> JSX | spec }
- -> ReactComponent { | jsProps }
-- |
-- | Internal utility or FFI functions
-- |
-buildStateUpdate
- :: forall props state action
- . StateUpdate props state action
- -> { state :: Nullable state
- , effects :: Nullable (Self props state action -> Effect Unit)
- }
-buildStateUpdate = case _ of
- NoUpdate ->
- { state: null
- , effects: null
- }
- Update state_ ->
- { state: notNull state_
- , effects: null
- }
- SideEffects effects ->
- { state: null
- , effects: notNull effects
- }
- UpdateAndSideEffects state_ effects ->
- { state: notNull state_
- , effects: notNull effects
- }
-
-foreign import send_
- :: forall props state action
- . Fn1
- (StateUpdate props state action
- -> { state :: Nullable state
- , effects :: Nullable (Self props state action -> Effect Unit)
- })
- (EffectFn2
- (Self props state action)
- action
- Unit)
-
-foreign import keyed_ :: Fn2 String JSX JSX
-
-foreign import element_
- :: forall props
- . Fn2 (ReactComponent { | props }) { | props } JSX
+foreign import memo_
+ :: forall hooks props
+ . EffectFn1
+ (Component props hooks)
+ (Component props hooks)
-foreign import elementKeyed_
- :: forall props
- . Fn2 (ReactComponent { | props }) { key :: String | props } JSX
+foreign import unsafeSetDisplayName
+ :: forall hooks props
+ . EffectFn2 String (Component props hooks) (Component props hooks)
-foreign import warningDefaultUpdate
- :: forall props state action
+foreign import useState_
+ :: forall state
. EffectFn2
- (Self props state action)
- action
+ (forall a b. Fn2 a b (Tuple a b))
+ state
+ (Tuple state ((state -> state) -> Effect Unit))
+
+foreign import useEffect_
+ :: EffectFn2
+ (Effect (Effect Unit))
+ (Array Key)
Unit
-foreign import warningUnmountedComponentAction
- :: forall props state action
- . EffectFn2
- (Self props state action)
- action
+foreign import useLayoutEffect_
+ :: EffectFn2
+ (Effect (Effect Unit))
+ (Array Key)
Unit
-foreign import warningFailedAsyncAction
- :: forall props state action
+foreign import useReducer_
+ :: forall state action
+ . EffectFn3
+ (forall a b. Fn2 a b (Tuple a b))
+ (Fn2 state action state)
+ state
+ (Tuple state (action -> Effect Unit))
+
+foreign import readRef_
+ :: forall a
+ . EffectFn1
+ (Ref a)
+ a
+
+foreign import writeRef_
+ :: forall a
. EffectFn2
- (Self props state action)
- Error
+ (Ref a)
+ a
Unit
+
+foreign import useRef_
+ :: forall a
+ . EffectFn1
+ a
+ (Ref a)
+
+-- foreign import useContext_
+-- :: forall a
+-- . EffectFn1
+-- (Context a)
+-- a
+
+foreign import useMemo_
+ :: forall a
+ . EffectFn2
+ (Unit -> a)
+ (Array Key)
+ a
+
+foreign import keyed_ :: Fn2 String JSX JSX
+
+foreign import element_
+ :: forall props
+ . Fn2 (EffectFn1 { | props } JSX) { | props } JSX
+
+foreign import elementKeyed_
+ :: forall hooks props
+ . Fn2 (Component {| props } hooks) { key :: String | props } JSX
diff --git a/src/React/Basic/Compat.purs b/src/React/Basic/Compat.purs
new file mode 100644
index 0000000..94e951a
--- /dev/null
+++ b/src/React/Basic/Compat.purs
@@ -0,0 +1,43 @@
+module React.Basic.Compat
+ ( UseStatefulComponent
+ , component
+ , stateless
+ , module React.Basic
+ ) where
+
+import Prelude
+
+import Effect (Effect)
+import Effect.Unsafe (unsafePerformEffect)
+import React.Basic (Component, JSX, UseEffect, UseState, element, elementKeyed, empty, fragment, keyed)
+import React.Basic (Tuple(..), bind, discard, pure, component, toKey, useEffect, useState) as React
+
+type UseStatefulComponent state = (UseEffect (UseState state Unit))
+
+-- | Supports a common subset of the v2 API to ease the upgrade process
+component
+ :: forall props state
+ . { displayName :: String
+ , initialState :: {| state }
+ , receiveProps :: { props :: {| props }, state :: {| state }, setState :: ({| state } -> {| state }) -> Effect Unit } -> Effect Unit
+ , render :: { props :: {| props }, state :: {| state }, setState :: ({| state } -> {| state }) -> Effect Unit } -> JSX
+ }
+ -> Component {| props } (UseStatefulComponent {| state })
+component { displayName, initialState, receiveProps, render } = unsafePerformEffect do
+ React.component displayName \props -> React.do
+ React.Tuple state setState <- React.useState initialState
+ React.useEffect [React.toKey props, React.toKey state] do
+ receiveProps { props, state, setState }
+ pure (pure unit)
+ React.pure (render { props, state, setState })
+
+-- | Supports a common subset of the v2 API to ease the upgrade process
+stateless
+ :: forall props
+ . { displayName :: String
+ , render :: {| props } -> JSX
+ }
+ -> Component {| props } Unit
+stateless { displayName, render } = unsafePerformEffect do
+ React.component displayName \props -> React.do
+ React.pure (render props)
diff --git a/src/React/Basic/DOM.js b/src/React/Basic/DOM.js
index eee88bc..f4b8acd 100644
--- a/src/React/Basic/DOM.js
+++ b/src/React/Basic/DOM.js
@@ -14,14 +14,10 @@ exports.unmountComponentAtNode_ = function(node) {
return ReactDOM.unmountComponentAtNode(node);
};
-exports.findDOMNode_ = function(instance) {
- return ReactDOM.findDOMNode(instance);
-};
-
exports.createPortal_ = function(jsx, node) {
return ReactDOM.createPortal(jsx, node);
};
exports.mergeStyles = function(styles) {
- return Object.assign.apply(null, [ {} ].concat(styles));
+ return Object.assign.apply(null, [{}].concat(styles));
};
diff --git a/src/React/Basic/DOM.purs b/src/React/Basic/DOM.purs
index 85add7e..32a0d7d 100644
--- a/src/React/Basic/DOM.purs
+++ b/src/React/Basic/DOM.purs
@@ -12,7 +12,6 @@ module React.Basic.DOM
, hydrate
, hydrate'
, unmount
- , findDOMNode
, createPortal
, text
, css
@@ -22,18 +21,14 @@ module React.Basic.DOM
import Prelude
-import Data.Either (Either)
import Data.Function.Uncurried (Fn2, runFn2)
-import Data.Maybe (Maybe(..))
-import Data.Nullable (Nullable, toMaybe)
import Effect (Effect)
-import Effect.Exception (Error, throw, try)
import Effect.Uncurried (EffectFn1, EffectFn3, runEffectFn1, runEffectFn3)
-import React.Basic (ReactComponentInstance, JSX)
+import React.Basic (JSX)
import React.Basic.DOM.Generated (Props_a, Props_abbr, Props_address, Props_area, Props_article, Props_aside, Props_audio, Props_b, Props_base, Props_bdi, Props_bdo, Props_blockquote, Props_body, Props_br, Props_button, Props_canvas, Props_caption, Props_cite, Props_code, Props_col, Props_colgroup, Props_data, Props_datalist, Props_dd, Props_del, Props_details, Props_dfn, Props_dialog, Props_div, Props_dl, Props_dt, Props_em, Props_embed, Props_fieldset, Props_figcaption, Props_figure, Props_footer, Props_form, Props_h1, Props_h2, Props_h3, Props_h4, Props_h5, Props_h6, Props_head, Props_header, Props_hgroup, Props_hr, Props_html, Props_i, Props_iframe, Props_img, Props_input, Props_ins, Props_kbd, Props_keygen, Props_label, Props_legend, Props_li, Props_link, Props_main, Props_map, Props_mark, Props_math, Props_menu, Props_menuitem, Props_meta, Props_meter, Props_nav, Props_noscript, Props_object, Props_ol, Props_optgroup, Props_option, Props_output, Props_p, Props_param, Props_picture, Props_pre, Props_progress, Props_q, Props_rb, Props_rp, Props_rt, Props_rtc, Props_ruby, Props_s, Props_samp, Props_script, Props_section, Props_select, Props_slot, Props_small, Props_source, Props_span, Props_strong, Props_style, Props_sub, Props_summary, Props_sup, Props_svg, Props_table, Props_tbody, Props_td, Props_template, Props_textarea, Props_tfoot, Props_th, Props_thead, Props_time, Props_title, Props_tr, Props_track, Props_u, Props_ul, Props_var, Props_video, Props_wbr, a, a_, abbr, abbr_, address, address_, area, article, article_, aside, aside_, audio, audio_, b, b_, base, bdi, bdi_, bdo, bdo_, blockquote, blockquote_, body, body_, br, button, button_, canvas, canvas_, caption, caption_, cite, cite_, code, code_, col, colgroup, colgroup_, data', data_, datalist, datalist_, dd, dd_, del, del_, details, details_, dfn, dfn_, dialog, dialog_, div, div_, dl, dl_, dt, dt_, em, em_, embed, fieldset, fieldset_, figcaption, figcaption_, figure, figure_, footer, footer_, form, form_, h1, h1_, h2, h2_, h3, h3_, h4, h4_, h5, h5_, h6, h6_, head, head_, header, header_, hgroup, hgroup_, hr, html, html_, i, i_, iframe, iframe_, img, input, ins, ins_, kbd, kbd_, keygen, keygen_, label, label_, legend, legend_, li, li_, link, main, main_, map, map_, mark, mark_, math, math_, menu, menu_, menuitem, menuitem_, meta, meter, meter_, nav, nav_, noscript, noscript_, object, object_, ol, ol_, optgroup, optgroup_, option, option_, output, output_, p, p_, param, picture, picture_, pre, pre_, progress, progress_, q, q_, rb, rb_, rp, rp_, rt, rt_, rtc, rtc_, ruby, ruby_, s, s_, samp, samp_, script, script_, section, section_, select, select_, slot, slot_, small, small_, source, span, span_, strong, strong_, style, style_, sub, sub_, summary, summary_, sup, sup_, svg, svg_, table, table_, tbody, tbody_, td, td_, template, template_, textarea, textarea_, tfoot, tfoot_, th, th_, thead, thead_, time, time_, title, title_, tr, tr_, track, u, u_, ul, ul_, var, var_, video, video_, wbr) as Generated
import React.Basic.DOM.Internal (CSS, SharedProps, unsafeCreateDOMComponent) as Internal
import Unsafe.Coerce (unsafeCoerce)
-import Web.DOM (Element, Node)
+import Web.DOM (Element)
-- | Render or update/re-render a component tree into
-- | a DOM element.
@@ -85,21 +80,6 @@ unmount = runEffectFn1 unmountComponentAtNode_
foreign import unmountComponentAtNode_ :: EffectFn1 Element Boolean
--- | Returns the current DOM node associated with the given
--- | instance, or an Error if no node was found or the given
--- | instance was not mounted.
--- |
--- | Note: Relies on `ReactDOM.findDOMNode`
-findDOMNode :: ReactComponentInstance -> Effect (Either Error Node)
-findDOMNode instance_ = try do
- node <- runEffectFn1 findDOMNode_ instance_
- case toMaybe node of
- Nothing -> throw "Node not found."
- Just n -> pure n
-
--- | Warning: Relies on `ReactDOM.findDOMNode` which may throw exceptions
-foreign import findDOMNode_ :: EffectFn1 ReactComponentInstance (Nullable Node)
-
-- | Divert a render tree into a separate DOM node. The node's
-- | content will be overwritten and managed by React, similar
-- | to `render` and `hydrate`.
diff --git a/src/React/Basic/DOM/Events.purs b/src/React/Basic/DOM/Events.purs
index 55fa568..df19d27 100644
--- a/src/React/Basic/DOM/Events.purs
+++ b/src/React/Basic/DOM/Events.purs
@@ -1,7 +1,9 @@
-- | This module defines safe DOM event function and property accessors.
module React.Basic.DOM.Events
- ( bubbles
+ ( capture
+ , capture_
+ , bubbles
, cancelable
, eventPhase
, eventPhaseNone
@@ -44,11 +46,18 @@ import Prelude
import Data.Maybe (Maybe)
import Data.Nullable (toMaybe)
+import Effect (Effect)
import Effect.Unsafe (unsafePerformEffect)
-import React.Basic.Events (EventFn, SyntheticEvent, unsafeEventFn)
+import React.Basic.Events (EventFn, EventHandler, SyntheticEvent, handler, unsafeEventFn)
import Unsafe.Coerce (unsafeCoerce)
import Web.Event.Internal.Types (Event, EventTarget)
+capture :: forall a. EventFn SyntheticEvent a -> (a -> Effect Unit) -> EventHandler
+capture eventFn = handler (preventDefault >>> stopPropagation >>> eventFn)
+
+capture_ :: Effect Unit -> EventHandler
+capture_ = capture identity <<< const
+
-- | General event fields
bubbles :: EventFn SyntheticEvent Boolean
diff --git a/src/React/Basic/DOM/Internal.purs b/src/React/Basic/DOM/Internal.purs
index 156dd44..8c3ff52 100644
--- a/src/React/Basic/DOM/Internal.purs
+++ b/src/React/Basic/DOM/Internal.purs
@@ -1,8 +1,12 @@
module React.Basic.DOM.Internal where
-import React.Basic (ReactComponent)
+import Prelude
+
+import Data.Nullable (Nullable)
+import React.Basic (Component, Ref)
import React.Basic.Events (EventHandler)
import Unsafe.Coerce (unsafeCoerce)
+import Web.DOM (Node)
-- | An abstract type representing records of CSS attributes.
foreign import data CSS :: Type
@@ -12,6 +16,9 @@ type SharedProps specific =
-- | `key` is not really a DOM attribute - React intercepts it
( key :: String
+ -- | `ref` is not really a DOM attribute - React intercepts it
+ , ref :: Ref (Nullable Node)
+
, about :: String
, acceptCharset :: String
, accessKey :: String
@@ -93,5 +100,5 @@ type SharedProps specific =
| specific
)
-unsafeCreateDOMComponent :: forall props. String -> ReactComponent props
+unsafeCreateDOMComponent :: forall props. String -> Component props Unit
unsafeCreateDOMComponent = unsafeCoerce