diff --git a/examples/actions/.gitignore b/examples/actions/.gitignore new file mode 100644 index 0000000..645684d --- /dev/null +++ b/examples/actions/.gitignore @@ -0,0 +1,4 @@ +output +html/index.js +package-lock.json +node_modules diff --git a/examples/actions/Makefile b/examples/actions/Makefile new file mode 100644 index 0000000..ecacfbe --- /dev/null +++ b/examples/actions/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/actions/README.md b/examples/actions/README.md new file mode 100644 index 0000000..f2418c0 --- /dev/null +++ b/examples/actions/README.md @@ -0,0 +1,12 @@ +# Counter Example + +## Building + +``` +npm install +make all +``` + +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/actions/html/index.html b/examples/actions/html/index.html new file mode 100644 index 0000000..6b93b7c --- /dev/null +++ b/examples/actions/html/index.html @@ -0,0 +1,10 @@ + + + + react-basic example + + +
+ + + diff --git a/examples/actions/package.json b/examples/actions/package.json new file mode 100644 index 0000000..583c22b --- /dev/null +++ b/examples/actions/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "react": "16.6.0", + "react-dom": "16.6.0" + }, + "devDependencies": { + "browserify": "16.2.3" + } +} diff --git a/examples/actions/src/Actions.purs b/examples/actions/src/Actions.purs new file mode 100644 index 0000000..27d6090 --- /dev/null +++ b/examples/actions/src/Actions.purs @@ -0,0 +1,37 @@ +module Actions where + +import Prelude + +import Effect.Console (log) +import React.Basic (Component, JSX, StateUpdate(..), createComponent, make, runUpdate) +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 + +actions :: Props -> JSX +actions = make component { initialState, render } + where + initialState = { counter: 0 } + + update self = case _ of + Increment -> + UpdateAndSideEffects + (self.state { counter = self.state.counter + 1 }) + \{ state } -> log $ "Count: " <> show state.counter + + send = runUpdate update + + render self = + R.button + { onClick: capture_ $ send self Increment + , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ] + } diff --git a/examples/actions/src/Main.purs b/examples/actions/src/Main.purs new file mode 100644 index 0000000..4aa5de5 --- /dev/null +++ b/examples/actions/src/Main.purs @@ -0,0 +1,22 @@ +module Main where + +import Prelude + +import Actions (actions) +import Data.Maybe (Maybe(..)) +import Effect (Effect) +import Effect.Exception (throw) +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 -> + let app = actions { label: "Increment" } + in render app c diff --git a/examples/async/src/AsyncCounter.purs b/examples/async/src/AsyncCounter.purs index ff0be72..89a44d2 100644 --- a/examples/async/src/AsyncCounter.purs +++ b/examples/async/src/AsyncCounter.purs @@ -5,9 +5,10 @@ import Prelude import Effect.Aff (Milliseconds(..), delay) import Effect.Class (liftEffect) import Effect.Console (log) -import React.Basic (Component, JSX, StateUpdate(..), capture_, createComponent, fragment, keyed, make) +import React.Basic (Component, JSX, createComponent, fragment, keyed, make) import React.Basic.Components.Async (asyncWithLoader) import React.Basic.DOM as R +import React.Basic.DOM.Events (capture_) component :: Component Props component = createComponent "AsyncCounter" @@ -16,17 +17,10 @@ type Props = { label :: String } -data Action - = Increment - asyncCounter :: Props -> JSX -asyncCounter = make component { initialState, update, render } +asyncCounter = make component { initialState, render } where - initialState = { counter: 0 } - - update self = case _ of - Increment -> - Update self.state { counter = self.state.counter + 1 } + initialState = 0 render self = fragment @@ -36,16 +30,16 @@ asyncCounter = make component { initialState, update, render } , R.li_ [ R.text "\"done\" should only be logged to the console once for any loading period (in-flight requests get cancelled as the next request starts)" ] ] , R.button - { onClick: capture_ self Increment - , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ] + { onClick: capture_ $ self.setState (_ + 1) + , children: [ R.text (self.props.label <> ": " <> show self.state) ] } , R.text " " - , keyed (show self.state.counter) $ + , keyed (show self.state) $ asyncWithLoader (R.text "Loading...") do liftEffect $ log "start" delay $ Milliseconds 2000.0 liftEffect $ log "done" - pure $ R.text $ "Done: " <> show self.state.counter + pure $ R.text $ "Done: " <> show self.state ] diff --git a/examples/component/package.json b/examples/component/package.json index 624a6f3..18e1a87 100644 --- a/examples/component/package.json +++ b/examples/component/package.json @@ -4,6 +4,6 @@ "react-dom": "^16.4.2" }, "devDependencies": { - "browserify": "^16.2.2" + "browserify": "16.2.3" } } diff --git a/examples/component/src/ToggleButton.purs b/examples/component/src/ToggleButton.purs index 49430ae..3a1736d 100644 --- a/examples/component/src/ToggleButton.purs +++ b/examples/component/src/ToggleButton.purs @@ -3,8 +3,9 @@ module ToggleButton where import Prelude import Effect.Console (log) -import React.Basic (Component, JSX, StateUpdate(..), capture_, createComponent, make) +import React.Basic (Component, JSX, createComponent, make, readState) import React.Basic.DOM as R +import React.Basic.DOM.Events (capture_) component :: Component Props component = createComponent "ToggleButton" @@ -13,25 +14,18 @@ type Props = { label :: String } -data Action - = Toggle - toggleButton :: Props -> JSX toggleButton = make component { initialState: { on: false } - , 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 + { onClick: capture_ $ + self.setStateThen _ { on = not self.state.on } do + nextState <- readState self + log $ "next state: " <> show nextState , children: [ R.text self.props.label , R.text if self.state.on diff --git a/examples/controlled-input/src/ControlledInput.purs b/examples/controlled-input/src/ControlledInput.purs index 7824483..88b0e99 100644 --- a/examples/controlled-input/src/ControlledInput.purs +++ b/examples/controlled-input/src/ControlledInput.purs @@ -3,10 +3,10 @@ module ControlledInput where import Prelude import Data.Maybe (Maybe(..), fromMaybe, maybe) -import React.Basic (Component, JSX, StateUpdate(..), capture, createComponent, make) +import React.Basic (Component, JSX, createComponent, make) import React.Basic as React import React.Basic.DOM as R -import React.Basic.DOM.Events (targetValue, timeStamp) +import React.Basic.DOM.Events (capture, targetValue, timeStamp) import React.Basic.Events (merge) component :: Component Props @@ -14,9 +14,6 @@ component = createComponent "ControlledInput" type Props = Unit -data Action - = ValueChanged String Number - controlledInput :: Props -> JSX controlledInput = make component { initialState: @@ -24,19 +21,16 @@ controlledInput = make component , timestamp: Nothing } - , update: \self -> case _ of - ValueChanged value timestamp -> - Update self.state - { value = value - , timestamp = Just timestamp - } - , render: \self -> React.fragment [ R.input { onChange: - capture self (merge { targetValue, timeStamp }) - \{ timeStamp, targetValue } -> ValueChanged (fromMaybe "" targetValue) timeStamp + capture (merge { targetValue, timeStamp }) + \{ timeStamp, targetValue } -> + self.setState _ + { value = fromMaybe "" targetValue + , timestamp = Just timeStamp + } , value: self.state.value } , R.p_ [ R.text ("Current value = " <> show self.state.value) ] diff --git a/examples/counter/src/Counter.purs b/examples/counter/src/Counter.purs index 07c0ba3..a395a7a 100644 --- a/examples/counter/src/Counter.purs +++ b/examples/counter/src/Counter.purs @@ -2,8 +2,9 @@ module Counter where import Prelude -import React.Basic (Component, JSX, StateUpdate(..), capture_, createComponent, make) +import React.Basic (Component, JSX, createComponent, make) import React.Basic.DOM as R +import React.Basic.DOM.Events (capture_) component :: Component Props component = createComponent "Counter" @@ -12,20 +13,13 @@ type Props = { label :: String } -data Action - = Increment - counter :: Props -> JSX -counter = make component { initialState, update, render } +counter = make component { initialState, render } where initialState = { counter: 0 } - update self = case _ of - Increment -> - Update self.state { counter = self.state.counter + 1 } - render self = R.button - { onClick: capture_ self Increment + { onClick: capture_ $ self.setState \s -> s { counter = s.counter + 1 } , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ] } diff --git a/generated-docs/React/Basic.md b/generated-docs/React/Basic.md index 6a79861..572fa88 100644 --- a/generated-docs/React/Basic.md +++ b/generated-docs/React/Basic.md @@ -3,7 +3,7 @@ #### `ComponentSpec` ``` purescript -type ComponentSpec props state action = (initialState :: state, update :: Self props state action -> action -> StateUpdate props state action, render :: Self props state action -> JSX, didMount :: Self props state action -> Effect Unit, shouldUpdate :: Self props state action -> { nextProps :: props, nextState :: state } -> Boolean, didUpdate :: Self props state action -> { prevProps :: props, prevState :: state } -> Effect Unit, willUnmount :: Self props state action -> Effect Unit) +type ComponentSpec props state = (initialState :: state, render :: Self props state -> JSX, didMount :: Self props state -> Effect Unit, shouldUpdate :: Self props state -> { nextProps :: props, nextState :: state } -> Boolean, didUpdate :: Self props state -> { prevProps :: props, prevState :: state } -> Effect Unit, willUnmount :: Self props state -> Effect Unit) ``` `ComponentSpec` represents a React-Basic component implementation. @@ -11,18 +11,11 @@ type ComponentSpec props state action = (initialState :: state, update :: Self p 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 -and will emit a warning. +is required (the compiler enforces this). - `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`. - `didMount` @@ -48,26 +41,19 @@ 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 + { onClick: capture_ $ self.setState \s -> s { counter + 1 } , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ] } } ``` -This example component overrides `initialState`, `update`, and `render`. +This example component overrides `initialState` 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`.__ @@ -123,25 +109,10 @@ __*For the curious:* This is the "class" React will use to render and 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` - -``` purescript -data StateUpdate props state action - = NoUpdate - | Update state - | SideEffects (Self props state action -> Effect Unit) - | UpdateAndSideEffects state (Self props state action -> Effect Unit) -``` - -Used by the `update` function to describe the kind of state update and/or side -effects desired. - -__*See also:* `ComponentSpec`, `capture`__ - #### `Self` ``` purescript -type Self props state action = { props :: props, state :: state, instance_ :: ReactComponentInstance props state action } +type Self props state = { props :: props, state :: state, setState :: (state -> state) -> Effect Unit, setStateThen :: (state -> state) -> Effect Unit -> Effect Unit, instance_ :: ReactComponentInstance props state } ``` `Self` represents the component instance at a particular point in time. @@ -150,82 +121,19 @@ type Self props state action = { props :: props, state :: state, instance_ :: Re - A snapshot of `props` taken when this `Self` was created. - `state` - A snapshot of `state` taken when this `Self` was created. +- `setState` + - Update the component's state using the given function. +- `setStateThen` + - Update the component's state using the given function. The given effects are performed after any resulting rerenders are completed. Be careful to avoid using stale props or state in the effect callback. Use `readProps` or `readState` to aquire the latest values. - `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` - -``` purescript -send :: forall props state action. Self props state action -> action -> Effect Unit -``` - -Dispatch an `action` into the component to be handled by `update`. - -__*See also:* `update`, `capture`__ - -#### `sendAsync` - -``` purescript -sendAsync :: forall props state action. Self props state action -> Aff 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` - -``` purescript -capture :: forall props state action a. Self props state action -> EventFn SyntheticEvent a -> (a -> action) -> EventHandler -``` - -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_` - -``` purescript -capture_ :: forall props state action. Self props state action -> action -> EventHandler -``` - -Like `capture`, but for actions which don't need to extract information from the Event. - -__*See also:* `update`, `capture`, `monitor_`__ - -#### `monitor` - -``` purescript -monitor :: forall props state action a. Self props state action -> EventFn SyntheticEvent a -> (a -> action) -> EventHandler -``` - -Like `capture`, but does not cancel the event. - -__*See also:* `update`, `capture`, `monitor\_`__ - -#### `monitor_` - -``` purescript -monitor_ :: forall props state action. Self props state action -> action -> EventHandler -``` - -Like `capture_`, but does not cancel the event. - -__*See also:* `update`, `monitor`, `capture_`, `React.Basic.Events`__ - #### `readProps` ``` purescript -readProps :: forall props state action. Self props state action -> Effect props +readProps :: forall props state. Self props state -> Effect props ``` Read the most up to date `props` directly from the component instance @@ -239,7 +147,7 @@ __*See also:* `Self`__ #### `readState` ``` purescript -readState :: forall props state action. Self props state action -> Effect state +readState :: forall props state. Self props state -> Effect state ``` Read the most up to date `state` directly from the component instance @@ -250,10 +158,35 @@ _Note: This function is for specific, asynchronous edge cases. __*See also:* `Self`__ +#### `StateUpdate` + +``` purescript +data StateUpdate props state + = NoUpdate + | Update state + | SideEffects (Self props state -> Effect Unit) + | UpdateAndSideEffects state (Self props state -> Effect Unit) +``` + +Describes a state update for use with `runUpdate`. + +__*See also:* `runUpdate`__ + +#### `runUpdate` + +``` purescript +runUpdate :: forall props state action. (Self props state -> action -> StateUpdate props state) -> Self props state -> action -> Effect Unit +``` + +Creates a send/dispatch function which sends actions through the given +`update` function. + +__*See also:* `StateUpdate`__ + #### `make` ``` purescript -make :: forall spec spec_ props state action. Union spec spec_ (ComponentSpec props state action) => Component props -> { initialState :: state, render :: Self props state action -> JSX | spec } -> props -> JSX +make :: forall spec spec_ props state. Union spec spec_ (ComponentSpec props state) => Component props -> { initialState :: state, render :: Self props state -> JSX | spec } -> props -> JSX ``` Turn a `Component` and `ComponentSpec` into a usable render function. @@ -267,20 +200,13 @@ 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 + { onClick: capture_ $ self.setState \s -> s { counter = s.counter + 1 } , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ] } } @@ -410,7 +336,7 @@ __*See also:* `displayNameFromSelf`, `createComponent`__ #### `displayNameFromSelf` ``` purescript -displayNameFromSelf :: forall props state action. Self props state action -> String +displayNameFromSelf :: forall props state. Self props state -> String ``` Retrieve the Display Name from a `Self`. Useful for debugging and improving @@ -436,7 +362,7 @@ __*See also:* `element`, `toReactComponent`__ #### `ReactComponentInstance` ``` purescript -data ReactComponentInstance :: Type -> Type -> Type -> Type +data ReactComponentInstance :: Type -> Type -> Type ``` An opaque representation of a React component's instance (`this` in the JavaScript @@ -446,7 +372,7 @@ caution. #### `toReactComponent` ``` purescript -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 } +toReactComponent :: forall spec spec_ jsProps props state. Union spec spec_ (ComponentSpec props state) => ({ | jsProps } -> props) -> Component props -> { render :: Self props state -> JSX | spec } -> ReactComponent { | jsProps } ``` Convert a React-Basic `ComponentSpec` to a JavaScript-friendly React component. diff --git a/generated-docs/React/Basic/Compat.md b/generated-docs/React/Basic/Compat.md index ce273a6..015ab68 100644 --- a/generated-docs/React/Basic/Compat.md +++ b/generated-docs/React/Basic/Compat.md @@ -12,7 +12,7 @@ type Component = ReactComponent #### `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 } -> ReactComponent { | props } +component :: forall props state. { displayName :: String, initialState :: { | state }, receiveProps :: Self { | props } { | state } -> Effect Unit, render :: Self { | props } { | state } -> JSX } -> ReactComponent { | props } ``` Supports a common subset of the v2 API to ease the upgrade process @@ -28,6 +28,27 @@ Supports a common subset of the v2 API to ease the upgrade process ### Re-exported from React.Basic: +#### `Self` + +``` purescript +type Self props state = { props :: props, state :: state, setState :: (state -> state) -> Effect Unit, setStateThen :: (state -> state) -> Effect Unit -> Effect Unit, instance_ :: ReactComponentInstance props state } +``` + +`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. +- `setState` + - Update the component's state using the given function. +- `setStateThen` + - Update the component's state using the given function. The given effects are performed after any resulting rerenders are completed. Be careful to avoid using stale props or state in the effect callback. Use `readProps` or `readState` to aquire the latest values. +- `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`__ + #### `JSX` ``` purescript diff --git a/generated-docs/React/Basic/DOM.md b/generated-docs/React/Basic/DOM.md index 1b1e228..4b68734 100644 --- a/generated-docs/React/Basic/DOM.md +++ b/generated-docs/React/Basic/DOM.md @@ -74,7 +74,7 @@ __*Note:* Relies on `ReactDOM.unmountComponentAtNode`__ #### `findDOMNode` ``` purescript -findDOMNode :: forall props state action. ReactComponentInstance props state action -> Effect (Either Error Node) +findDOMNode :: forall props state. ReactComponentInstance props state -> Effect (Either Error Node) ``` Returns the current DOM node associated with the given diff --git a/generated-docs/React/Basic/DOM/Events.md b/generated-docs/React/Basic/DOM/Events.md index 116b4e5..16435e0 100644 --- a/generated-docs/React/Basic/DOM/Events.md +++ b/generated-docs/React/Basic/DOM/Events.md @@ -2,6 +2,29 @@ This module defines safe DOM event function and property accessors. +#### `capture` + +``` purescript +capture :: forall a. EventFn SyntheticEvent a -> (a -> Effect Unit) -> EventHandler +``` + +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_` + +``` purescript +capture_ :: Effect Unit -> EventHandler +``` + +Like `capture`, but for actions which don't need to extract information from the Event. + +__*See also:* `update`, `capture`, `monitor_`__ + #### `bubbles` ``` purescript diff --git a/package-lock.json b/package-lock.json index a609272..6f7a484 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1498,12 +1498,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1518,17 +1520,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1645,7 +1650,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1657,6 +1663,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1671,6 +1678,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1678,12 +1686,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -1702,6 +1712,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1782,7 +1793,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1794,6 +1806,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1915,6 +1928,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/src/React/Basic.js b/src/React/Basic.js index 9265dfd..803c9c3 100644 --- a/src/React/Basic.js +++ b/src/React/Basic.js @@ -8,10 +8,24 @@ exports.createComponent = (function() { // (`this`-dependent, defined outside `createComponent` // for a slight performance boost) function toSelf() { + var instance = this; + var setStateThen = function(update) { + return function(effects) { + return function() { + instance.setState(function(state) { + return { $$state: update(state.$$state) }; + }, effects); + }; + }; + }; var self = { - props: this.props.$$props, - state: this.state === null ? null : this.state.$$state, - instance_: this + props: instance.props.$$props, + state: instance.state === null ? null : instance.state.$$state, + setState: function(update) { + return setStateThen(update)(undefined); + }, + setStateThen: setStateThen, + instance_: instance }; return self; } @@ -28,9 +42,9 @@ exports.createComponent = (function() { return shouldUpdate === undefined ? true : shouldUpdate(this.toSelf())({ - nextProps: nextProps.$$props, - nextState: nextState === null ? null : nextState.$$state - }); + nextProps: nextProps.$$props, + nextState: nextState === null ? null : nextState.$$state + }); } function componentDidUpdate(prevProps, prevState) { @@ -84,42 +98,6 @@ exports.createComponent = (function() { }; })(); -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 function() { return self.instance_.props.$$props; @@ -133,12 +111,35 @@ exports.readState = function(self) { }; }; +exports.runUpdate_ = function(update, self, action) { + var sideEffects = null; + self.instance_.setState( + function(s) { + var setStateSelf = self.instance_.toSelf(); + setStateSelf.state = s.$$state; + var updates = update(setStateSelf, 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.make = function(_unionDict) { return function($$type) { return function($$spec) { var $$specPadded = { initialState: $$spec.initialState, - update: $$spec.update, render: $$spec.render, didMount: $$spec.didMount, shouldUpdate: $$spec.shouldUpdate, @@ -189,7 +190,6 @@ exports.toReactComponent = function(_unionDict) { return function($$spec) { var $$specPadded = { initialState: $$spec.initialState, - update: $$spec.update, render: $$spec.render, didMount: $$spec.didMount, shouldUpdate: $$spec.shouldUpdate, @@ -218,32 +218,3 @@ exports.toReactComponent = function(_unionDict) { }; }; }; - -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); -}; diff --git a/src/React/Basic.purs b/src/React/Basic.purs index 4977daa..4804238 100644 --- a/src/React/Basic.purs +++ b/src/React/Basic.purs @@ -2,16 +2,11 @@ module React.Basic ( ComponentSpec , createComponent , Component - , StateUpdate(..) , Self - , send - , sendAsync - , capture - , capture_ - , monitor - , monitor_ , readProps , readState + , StateUpdate(..) + , runUpdate , make , makeStateless , JSX @@ -29,14 +24,10 @@ module React.Basic import Prelude -import Data.Either (Either(..)) -import Data.Function.Uncurried (Fn1, Fn2, runFn1, runFn2) +import Data.Function.Uncurried (Fn2, mkFn2, runFn2) import Data.Nullable (Nullable, notNull, null) 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 Effect.Uncurried (EffectFn3, runEffectFn3) import Type.Row (class Union) -- | `ComponentSpec` represents a React-Basic component implementation. @@ -44,18 +35,11 @@ import Type.Row (class Union) -- | 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 --- | and will emit a warning. +-- | is required (the compiler enforces this). -- | -- | - `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`. -- | - `didMount` @@ -81,39 +65,31 @@ import Type.Row (class Union) -- | { 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 +-- | { onClick: capture_ $ self.setState \s -> s { counter + 1 } -- | , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ] -- | } -- | } -- | ``` -- | --- | This example component overrides `initialState`, `update`, and `render`. +-- | This example component overrides `initialState` 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 = +type ComponentSpec props state = ( initialState :: state - , update :: Self props state action -> action -> StateUpdate props state action - , render :: Self props state action -> JSX - , didMount :: Self props state action -> Effect Unit - , shouldUpdate :: Self props state action -> { nextProps :: props, nextState :: state } -> Boolean - , didUpdate :: Self props state action -> { prevProps :: props, prevState :: state } -> Effect Unit - , willUnmount :: Self props state action -> Effect Unit + , render :: Self props state -> JSX + , didMount :: Self props state -> Effect Unit + , shouldUpdate :: Self props state -> { nextProps :: props, nextState :: state } -> Boolean + , didUpdate :: Self props state -> { prevProps :: props, prevState :: state } -> Effect Unit + , willUnmount :: Self props state -> Effect Unit ) -- | Creates a `Component` with a given Display Name. @@ -158,82 +134,28 @@ foreign import createComponent -- | 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`, `capture`__ -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. +-- | - `setState` +-- | - Update the component's state using the given function. +-- | - `setStateThen` +-- | - Update the component's state using the given function. The given effects are performed after any resulting rerenders are completed. Be careful to avoid using stale props or state in the effect callback. Use `readProps` or `readState` to aquire the latest values. -- | - `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 props state action +type Self props state = + { props :: props + , state :: state + , setState :: (state -> state) -> Effect Unit + , setStateThen :: (state -> state) -> Effect Unit -> Effect Unit + , instance_ :: ReactComponentInstance props state } --- | 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) - --- | 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) - --- | 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 - --- | 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) - --- | 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 - -- | Read the most up to date `props` directly from the component instance -- | associated with this `Self`. -- | @@ -241,7 +163,7 @@ monitor_ self action = monitor self identity \_ -> action -- | Generally, the `props` snapshot on `Self` is sufficient. -- | -- | __*See also:* `Self`__ -foreign import readProps :: forall props state action. Self props state action -> Effect props +foreign import readProps :: forall props state. Self props state -> Effect props -- | Read the most up to date `state` directly from the component instance -- | associated with this `Self`. @@ -250,7 +172,28 @@ foreign import readProps :: forall props state action. Self props state action - -- | Generally, the `state` snapshot on `Self` is sufficient. -- | -- | __*See also:* `Self`__ -foreign import readState :: forall props state action. Self props state action -> Effect state +foreign import readState :: forall props state. Self props state -> Effect state + +-- | Describes a state update for use with `runUpdate`. +-- | +-- | __*See also:* `runUpdate`__ +data StateUpdate props state + = NoUpdate + | Update state + | SideEffects (Self props state -> Effect Unit) + | UpdateAndSideEffects state (Self props state -> Effect Unit) + +-- | Creates a send/dispatch function which sends actions through the given +-- | `update` function. +-- | +-- | __*See also:* `StateUpdate`__ +runUpdate + :: forall props state action + . (Self props state -> action -> StateUpdate props state) + -> Self props state + -> action + -> Effect Unit +runUpdate update = runEffectFn3 runUpdate_ (mkFn2 \self action -> buildStateUpdate (update self action)) -- | Turn a `Component` and `ComponentSpec` into a usable render function. -- | This is where you will want to provide customized implementations: @@ -263,20 +206,13 @@ foreign import readState :: forall props state action. Self props state action - -- | { 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 +-- | { onClick: capture_ $ self.setState \s -> s { counter = s.counter + 1 } -- | , children: [ R.text (self.props.label <> ": " <> show self.state.counter) ] -- | } -- | } @@ -284,10 +220,10 @@ foreign import readState :: forall props state action. Self props state action - -- | -- | __*See also:* `makeStateless`, `createComponent`, `Component`, `ComponentSpec`__ foreign import make - :: forall spec spec_ props state action - . Union spec spec_ (ComponentSpec props state action) + :: forall spec spec_ props state + . Union spec spec_ (ComponentSpec props state) => Component props - -> { initialState :: state, render :: Self props state action -> JSX | spec } + -> { initialState :: state, render :: Self props state -> JSX | spec } -> props -> JSX @@ -396,8 +332,8 @@ foreign import displayNameFromComponent -- | -- | __*See also:* `displayNameFromComponent`, `createComponent`__ foreign import displayNameFromSelf - :: forall props state action - . Self props state action + :: forall props state + . Self props state -> String -- | Represents a traditional React component. Useful for JavaScript interop and @@ -413,7 +349,7 @@ foreign import data ReactComponent :: Type -> Type -- | 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. -foreign import data ReactComponentInstance :: Type -> Type -> Type -> Type +foreign import data ReactComponentInstance :: Type -> Type -> Type -- | 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. @@ -425,11 +361,11 @@ foreign import data ReactComponentInstance :: Type -> Type -> Type -> Type -- | -- | __*See also:* `ReactComponent`__ foreign import toReactComponent - :: forall spec spec_ jsProps props state action - . Union spec spec_ (ComponentSpec props state action) + :: forall spec spec_ jsProps props state + . Union spec spec_ (ComponentSpec props state) => ({ | jsProps } -> props) -> Component props - -> { render :: Self props state action -> JSX | spec } + -> { render :: Self props state -> JSX | spec } -> ReactComponent { | jsProps } @@ -438,10 +374,10 @@ foreign import toReactComponent -- | buildStateUpdate - :: forall props state action - . StateUpdate props state action + :: forall props state + . StateUpdate props state -> { state :: Nullable state - , effects :: Nullable (Self props state action -> Effect Unit) + , effects :: Nullable (Self props state -> Effect Unit) } buildStateUpdate = case _ of NoUpdate -> @@ -461,17 +397,19 @@ buildStateUpdate = case _ of , effects: notNull effects } -foreign import send_ +foreign import runUpdate_ :: 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) + . EffectFn3 + (Fn2 + (Self props state) + action + { state :: Nullable state + , effects :: Nullable (Self props state -> Effect Unit) + } + ) + (Self props state) + action + Unit foreign import keyed_ :: Fn2 String JSX JSX @@ -482,24 +420,3 @@ foreign import element_ foreign import elementKeyed_ :: forall props . Fn2 (ReactComponent { | props }) { key :: String | props } JSX - -foreign import warningDefaultUpdate - :: forall props state action - . EffectFn2 - (Self props state action) - action - Unit - -foreign import warningUnmountedComponentAction - :: forall props state action - . EffectFn2 - (Self props state action) - action - Unit - -foreign import warningFailedAsyncAction - :: forall props state action - . EffectFn2 - (Self props state action) - Error - Unit diff --git a/src/React/Basic/Compat.purs b/src/React/Basic/Compat.purs index 789e148..0ab26a2 100644 --- a/src/React/Basic/Compat.purs +++ b/src/React/Basic/Compat.purs @@ -10,8 +10,8 @@ module React.Basic.Compat import Prelude import Effect (Effect) -import React.Basic (ReactComponent, StateUpdate(..), createComponent, send, toReactComponent) -import React.Basic (JSX, element, elementKeyed, empty, fragment, keyed) as CompatibleTypes +import React.Basic (ReactComponent, createComponent, toReactComponent) +import React.Basic (JSX, Self, element, elementKeyed, empty, fragment, keyed) as CompatibleTypes type Component = ReactComponent @@ -19,33 +19,26 @@ type Component = ReactComponent 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 } -> CompatibleTypes.JSX + , initialState :: {| state } + , receiveProps :: CompatibleTypes.Self {| props } {| state } -> Effect Unit + , render :: CompatibleTypes.Self {| props } {| state } -> CompatibleTypes.JSX } - -> ReactComponent { | props } + -> ReactComponent {| props } component { displayName, initialState, receiveProps, render } = toReactComponent identity (createComponent displayName) { initialState: initialState - , didMount: receiveProps <<< selfToLegacySelf - , didUpdate: const <<< receiveProps <<< selfToLegacySelf - , update: \self stateUpdate -> Update (stateUpdate self.state) - , render: render <<< selfToLegacySelf + , didMount: receiveProps + , didUpdate: const <<< receiveProps + , render: render } - 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 } -> CompatibleTypes.JSX + , render :: {| props } -> CompatibleTypes.JSX } - -> ReactComponent { | props } + -> ReactComponent {| props } stateless { displayName, render } = toReactComponent identity (createComponent displayName) { render: \self -> render self.props diff --git a/src/React/Basic/Components/Async.purs b/src/React/Basic/Components/Async.purs index 17f3991..7aad6e9 100644 --- a/src/React/Basic/Components/Async.purs +++ b/src/React/Basic/Components/Async.purs @@ -8,7 +8,7 @@ import Prelude import Data.Maybe (Maybe(..), fromMaybe) import Effect.Aff (Aff, Fiber, error, killFiber, launchAff, launchAff_) import Effect.Class (liftEffect) -import React.Basic (Component, JSX, StateUpdate(..), createComponent, empty, make, send) +import React.Basic (Component, JSX, createComponent, empty, make) component :: Component (Aff JSX) component = createComponent "Async" @@ -23,7 +23,6 @@ async = asyncWithLoader empty asyncWithLoader :: JSX -> Aff JSX -> JSX asyncWithLoader loader = make component { initialState - , update , render , didMount: launch -- , didUpdate: No! Implementing `didUpdate` breaks the @@ -38,24 +37,15 @@ asyncWithLoader loader = make component , pendingFiber: pure unit } - update { props, state } = case _ of - ReplaceFiber newFiber -> - UpdateAndSideEffects - state { jsx = Nothing, pendingFiber = newFiber } - \_ -> kill state.pendingFiber - - UpdateJSX jsx -> - Update - state { jsx = Just jsx } - render self = fromMaybe loader self.state.jsx launch self = do fiber <- launchAff do jsx <- self.props - liftEffect $ send self $ UpdateJSX jsx - send self $ ReplaceFiber fiber + liftEffect $ self.setState _ { jsx = Just jsx } + self.setStateThen _ { jsx = Nothing, pendingFiber = fiber } do + kill self.state.pendingFiber cleanup self = kill self.state.pendingFiber diff --git a/src/React/Basic/DOM.purs b/src/React/Basic/DOM.purs index 3b2d2d2..4c083ea 100644 --- a/src/React/Basic/DOM.purs +++ b/src/React/Basic/DOM.purs @@ -93,7 +93,7 @@ foreign import unmountComponentAtNode_ :: EffectFn1 Element Boolean -- | `React.Basic.DOM.Components.Ref` where possible__ -- | -- | __*Note:* Relies on `ReactDOM.findDOMNode`__ -findDOMNode :: forall props state action. ReactComponentInstance props state action -> Effect (Either Error Node) +findDOMNode :: forall props state. ReactComponentInstance props state -> Effect (Either Error Node) findDOMNode instance_ = try do node <- runEffectFn1 findDOMNode_ instance_ case toMaybe node of @@ -101,7 +101,7 @@ findDOMNode instance_ = try do Just n -> pure n -- | Warning: Relies on `ReactDOM.findDOMNode` which may throw exceptions -foreign import findDOMNode_ :: forall props state action. EffectFn1 (ReactComponentInstance props state action) (Nullable Node) +foreign import findDOMNode_ :: forall props state. EffectFn1 (ReactComponentInstance props state) (Nullable Node) -- | Divert a render tree into a separate DOM node. The node's -- | content will be overwritten and managed by React, similar diff --git a/src/React/Basic/DOM/Events.purs b/src/React/Basic/DOM/Events.purs index de227d2..6267525 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 @@ -45,12 +47,28 @@ 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) import Web.File.FileList (FileList) +-- | 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 a. EventFn SyntheticEvent a -> (a -> Effect Unit) -> EventHandler +capture eventFn = handler (preventDefault >>> stopPropagation >>> eventFn) + +-- | Like `capture`, but for actions which don't need to extract information from the Event. +-- | +-- | __*See also:* `update`, `capture`, `monitor_`__ +capture_ :: Effect Unit -> EventHandler +capture_ cb = capture identity \_ -> cb + -- | General event fields bubbles :: EventFn SyntheticEvent Boolean