From b13fcc232cef41abe355f08b9fe74cf10c965ad6 Mon Sep 17 00:00:00 2001 From: Nathan Faubion Date: Sun, 26 Nov 2017 16:49:07 -0800 Subject: [PATCH 1/5] Alternative class construction This removes `createClass` and `spec` as well as the dependency on `create-react-class` in favor of `component` and `pureComponent` which creates constructors that inherit from `React.Component` and `React.PureComponent` in an idiomatic way for React. This also obviates the need for some machinery like `ref` handling, which can be implemented with `purescript-refs` instead. --- bower.json | 4 +- package.json | 3 +- src/React.js | 154 +++++++++--------------- src/React.purs | 245 +++++++++++++-------------------------- src/React/DOM/Props.purs | 26 +---- 5 files changed, 146 insertions(+), 286 deletions(-) diff --git a/bower.json b/bower.json index 690f7b3..8c77b09 100644 --- a/bower.json +++ b/bower.json @@ -22,7 +22,9 @@ "purescript-unsafe-coerce": "^3.0.0", "purescript-exceptions": "^3.1.0", "purescript-maybe": "^3.0.0", - "purescript-nullable": "^3.0.0" + "purescript-nullable": "^3.0.0", + "purescript-foreign": "^4.0.1", + "purescript-typelevel-prelude": "^2.5.0" }, "devDependencies": { "purescript-console": "^3.0.0", diff --git a/package.json b/package.json index 8930ed5..fe70c4c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "purescript-react", "files": [], "peerDependencies": { - "react": "^16.0.0", - "create-react-class": "^15.6.0" + "react": "^16.0.0" } } diff --git a/src/React.js b/src/React.js index 84da9c5..4445587 100644 --- a/src/React.js +++ b/src/React.js @@ -2,21 +2,63 @@ "use strict"; var React = require("react"); -var createReactClass = require("create-react-class"); -function getProps(this_) { - return function(){ - return this_.props; +function createClass(baseClass) { + function bindProperty(instance, prop, value) { + switch (prop) { + case 'componentDidMount': + case 'componentDidUpdate': + case 'componentWillMount': + case 'componentWillUnmount': + case 'render': + case 'state': + instance[prop] = value; + break; + + case 'componentWillReceiveProps': + instance[prop] = function (a) { return value(a)(); }; + break; + + case 'componentDidCatch': + case 'componentWillUpdate': + case 'shouldComponentUpdate': + instance[prop] = function (a, b) { return value(a)(b)(); }; + break; + + default: + throw new Error('Not a component property: ' + prop); + } + } + + return function (displayName) { + return function (ctrFn) { + var Constructor = function (props) { + baseClass.call(this, props); + var spec = ctrFn(this)(); + for (var k in spec) { + bindProperty(this, k, spec[k]); + } + }; + + Constructor.displayName = displayName; + Constructor.prototype = Object.create(baseClass.prototype); + Constructor.prototype.constructor = Constructor; + + return Constructor; + }; }; } -exports.getProps = getProps; -function getRefs(this_) { +exports.componentImpl = createClass(React.Component); + +exports.pureComponentImpl = createClass(React.PureComponent); + +function getProps(this_) { return function(){ - return this_.refs; + return this_.props; }; } -exports.getRefs = getRefs; +exports.getProps = getProps; function childrenToArray(children) { var result = []; @@ -40,33 +82,10 @@ function getChildren(this_) { } exports.getChildren = getChildren; -function readRefImpl (this_) { - return function(name) { - return function() { - return this_[name]; - } - } -} -exports.readRefImpl = readRefImpl; - -function writeRef(this_) { - return function(name) { - return function(node) { - return function() { - this_[name] = node; - return {}; - } - } - } -} -exports.writeRef = writeRef; - function writeState(this_) { return function(state){ return function(){ - this_.setState({ - state: state - }); + this_.setState(state); return state; }; }; @@ -77,9 +96,7 @@ function writeStateWithCallback(this_, cb) { return function(state){ return function(cb){ return function() { - this_.setState({ - state: state - }, cb); + this_.setState(state, cb); return state; }; }; @@ -89,7 +106,7 @@ exports.writeStateWithCallback = writeStateWithCallback; function readState(this_) { return function(){ - return this_.state.state; + return this_.state; }; } exports.readState = readState; @@ -98,71 +115,13 @@ function transformState(this_){ return function(update){ return function(){ this_.setState(function(old, props){ - return {state: update(old.state)}; + return update(old); }); }; }; } exports.transformState = transformState; -function createClass(toNullable, spec) { - var didCatch = toNullable(spec.componentDidCatch) - - var result = { - displayName: spec.displayName, - render: function(){ - return spec.render(this)(); - }, - getInitialState: function(){ - return { - state: spec.getInitialState(this)() - }; - }, - componentWillMount: function(){ - return spec.componentWillMount(this)(); - }, - componentDidMount: function(){ - return spec.componentDidMount(this)(); - }, - componentDidCatch: didCatch - ? function(error, info) {return didCatch(this)(error)(info)(); } - : undefined, - componentWillReceiveProps: function(nextProps){ - return spec.componentWillReceiveProps(this)(nextProps)(); - }, - shouldComponentUpdate: function(nextProps, nextState){ - return spec.shouldComponentUpdate(this)(nextProps)(nextState.state)(); - }, - componentWillUpdate: function(nextProps, nextState){ - return spec.componentWillUpdate(this)(nextProps)(nextState.state)(); - }, - componentDidUpdate: function(prevProps, prevState){ - return spec.componentDidUpdate(this)(prevProps)(prevState.state)(); - }, - componentWillUnmount: function(){ - return spec.componentWillUnmount(this)(); - } - }; - - return createReactClass(result); -} -exports["createClass'"] = createClass; - -function capitalize(s) { - if (!s) - return s; - return s.charAt(0).toUpperCase() + s.slice(1); -}; - -function createClassStateless(dict) { - return function (f) { - if (!f.displayName) - f.displayName = capitalize(f.name); - return f; - }; -}; -exports.createClassStateless = createClassStateless; - function forceUpdateCbImpl(this_, cb) { this_.forceUpdate(function() { return cb(); @@ -198,11 +157,6 @@ function createElementDynamic(class_) { exports.createElementDynamic = createElementDynamic; exports.createElementTagNameDynamic = createElementDynamic; -function createFactory(class_) { - return React.createFactory(class_); -} -exports.createFactory = createFactory; - function preventDefault(event) { return function() { event.preventDefault(); diff --git a/src/React.purs b/src/React.purs index 1829cae..36deb59 100644 --- a/src/React.purs +++ b/src/React.purs @@ -17,13 +17,8 @@ module React , ReactState , ReactProps - , ReactRefs - - , Refs - , Ref , Render - , GetInitialState , ComponentWillMount , ComponentDidMount , ComponentDidCatch @@ -33,7 +28,6 @@ module React , ComponentDidUpdate , ComponentWillUnmount - , ReactSpec , ReactClass , Event @@ -42,12 +36,10 @@ module React , EventHandlerContext - , spec, spec' + , component + , pureComponent , getProps - , getRefs - , readRef - , writeRef , getChildren , readState @@ -62,14 +54,10 @@ module React , preventDefault , stopPropagation - , createClass - , createClassStateless - , createClassStateless' , createElement , createElementDynamic , createElementTagName , createElementTagNameDynamic - , createFactory , Children , childrenToArray @@ -79,11 +67,8 @@ import Prelude import Control.Monad.Eff (kind Effect, Eff) import Control.Monad.Eff.Exception (Error) -import Data.Function.Uncurried (Fn2, runFn2) -import Data.Maybe (Maybe(Nothing)) -import Data.Nullable (Nullable, toMaybe, toNullable) import Control.Monad.Eff.Uncurried (EffFn2, runEffFn2) -import Unsafe.Coerce (unsafeCoerce) +import Type.Row (class RowLacks) -- | Name of a tag. type TagName = String @@ -123,14 +108,6 @@ foreign import data ReactState :: # Effect -> Effect -- | This effect indicates that a computation may read the component props. foreign import data ReactProps :: Effect --- | This effect indicates that a computation may read the component refs. --- | --- | The first type argument is a row of access types (`Read`, `Write`). -foreign import data ReactRefs :: # Effect -> Effect - --- | The type of refs objects. -foreign import data Refs :: Type - -- | The type of DOM events. foreign import data Event :: Type @@ -159,7 +136,6 @@ type KeyboardEvent = type EventHandlerContext eff props state result = Eff ( props :: ReactProps - , refs :: ReactRefs ReadOnly , state :: ReactState ReadWrite | eff ) result @@ -177,191 +153,166 @@ instance intReactRender :: ReactRender Int instance numberReactRender :: ReactRender Number -- | A render function. -type Render props state render eff = - ReactThis props state -> +type Render render eff = Eff ( props :: ReactProps - , refs :: ReactRefs Disallowed , state :: ReactState ReadOnly | eff ) render --- | A get initial state function. -type GetInitialState props state eff = - ReactThis props state -> - Eff - ( props :: ReactProps - , state :: ReactState Disallowed - , refs :: ReactRefs Disallowed - | eff - ) state - -- | A component will mount function. -type ComponentWillMount props state eff = - ReactThis props state -> +type ComponentWillMount eff = Eff ( props :: ReactProps , state :: ReactState ReadWrite - , refs :: ReactRefs Disallowed | eff ) Unit -- | A component did mount function. -type ComponentDidMount props state eff = - ReactThis props state -> +type ComponentDidMount eff = Eff ( props :: ReactProps , state :: ReactState ReadWrite - , refs :: ReactRefs ReadOnly | eff ) Unit -type ComponentDidCatch props state eff = - ReactThis props state -> +type ComponentDidCatch eff = Error -> { componentStack :: String } -> Eff ( props :: ReactProps , state :: ReactState ReadWrite - , refs :: ReactRefs ReadOnly | eff ) Unit - -- | A component will receive props function. -type ComponentWillReceiveProps props state eff = - ReactThis props state -> +type ComponentWillReceiveProps props eff = props -> Eff ( props :: ReactProps , state :: ReactState ReadWrite - , refs :: ReactRefs ReadOnly | eff ) Unit -- | A should component update function. type ShouldComponentUpdate props state eff = - ReactThis props state -> props -> state -> Eff ( props :: ReactProps , state :: ReactState ReadWrite - , refs :: ReactRefs ReadOnly | eff ) Boolean -- | A component will update function. type ComponentWillUpdate props state eff = - ReactThis props state -> props -> state -> Eff ( props :: ReactProps , state :: ReactState ReadWrite - , refs :: ReactRefs ReadOnly | eff ) Unit -- | A component did update function. type ComponentDidUpdate props state eff = - ReactThis props state -> props -> state -> Eff ( props :: ReactProps , state :: ReactState ReadOnly - , refs :: ReactRefs ReadOnly | eff ) Unit -- | A component will unmount function. -type ComponentWillUnmount props state eff = - ReactThis props state -> +type ComponentWillUnmount eff = Eff ( props :: ReactProps , state :: ReactState ReadOnly - , refs :: ReactRefs ReadOnly | eff ) Unit --- | A specification of a component. -type ReactSpec props state render eff = - { render :: Render props state render eff - , displayName :: String - , getInitialState :: GetInitialState props state eff - , componentWillMount :: ComponentWillMount props state eff - , componentDidMount :: ComponentDidMount props state eff - , componentDidCatch :: Maybe (ComponentDidCatch props state eff) - , componentWillReceiveProps :: ComponentWillReceiveProps props state eff - , shouldComponentUpdate :: ShouldComponentUpdate props state eff +-- | Required fields for constructing a ReactClass. +type ReactSpecRequired state render eff r = + ( state :: state + , render :: Render render eff + | r + ) + +-- | Optional fields for constructing a ReactClass. +type ReactSpecOptional props state eff r = + ( componentWillMount :: ComponentWillMount eff + , componentDidMount :: ComponentDidMount eff + , componentDidCatch :: ComponentDidCatch eff + , componentWillReceiveProps :: ComponentWillReceiveProps props eff , componentWillUpdate :: ComponentWillUpdate props state eff , componentDidUpdate :: ComponentDidUpdate props state eff - , componentWillUnmount :: ComponentWillUnmount props state eff - } + , componentWillUnmount :: ComponentWillUnmount eff + | r + ) --- | Create a component specification with a provided state. -spec :: forall props state render eff. - ReactRender render => - state -> Render props state render eff -> ReactSpec props state render eff -spec state = spec' \_ -> pure state - --- | Create a component specification with a get initial state function. -spec' :: forall props state render eff. - ReactRender render => - GetInitialState props state eff -> - Render props state render eff -> - ReactSpec props state render eff -spec' getInitialState renderFn = - { render: renderFn - , displayName: "" - , getInitialState: getInitialState - , componentWillMount: \_ -> pure unit - , componentDidMount: \_ -> pure unit - , componentDidCatch: Nothing - , componentWillReceiveProps: \_ _ -> pure unit - , shouldComponentUpdate: \_ _ _ -> pure true - , componentWillUpdate: \_ _ _ -> pure unit - , componentDidUpdate: \_ _ _ -> pure unit - , componentWillUnmount: \_ -> pure unit - } +type ReactSpecShouldComponentUpdate props state eff = + ( shouldComponentUpdate :: ShouldComponentUpdate props state eff + ) --- | React class for components. -foreign import data ReactClass :: Type -> Type +type ReactSpecAll props state render eff = + ReactSpecRequired state render eff (ReactSpecOptional props state eff (ReactSpecShouldComponentUpdate props state eff)) --- | Read the component props. -foreign import getProps :: forall props state eff. - ReactThis props state -> - Eff (props :: ReactProps | eff) props - --- | Read the component refs. -foreign import getRefs :: forall props state access eff. - ReactThis props state -> - Eff (refs :: ReactRefs (read :: Read | access) | eff) Refs +type ReactSpecPure props state render eff = + ReactSpecRequired state render eff (ReactSpecOptional props state eff ()) --- | Ref type. You can store `Ref` types on `Refs` object (which in --- | corresponds to `this.refs`). Use `ReactDOM.refToNode` if you want to --- | store a `DOM.Node.Types.Node` -foreign import data Ref :: Type - -foreign import readRefImpl :: forall props state access eff. +-- | The signature for a ReactClass constructor. A constructor takes the +-- | `ReactThis` context and returns a record with appropriate lifecycle +-- | methods. +type ReactClassConstructor props state render eff r = ReactThis props state -> + Eff + ( props :: ReactProps + , state :: ReactState Disallowed + | eff + ) (Record (ReactSpecRequired state render eff r)) + +-- | Creates a `ReactClass`` inherited from `React.Component`. +component + :: forall props state render eff r x + . Union (ReactSpecRequired (Record state) render eff r) x (ReactSpecAll (Record props) (Record state) render eff) + => RowLacks "children" props + => RowLacks "key" props + => ReactRender render + => String + -> ReactClassConstructor (Record props) (Record state) render eff r + -> ReactClass (Record props) +component = componentImpl + +-- | Creates a `ReactClass`` inherited from `React.PureComponent`. +pureComponent + :: forall props state render eff r x + . Union (ReactSpecRequired (Record state) render eff r) x (ReactSpecPure (Record props) (Record state) render eff) + => RowLacks "children" props + => RowLacks "key" props + => ReactRender render + => String + -> ReactClassConstructor (Record props) (Record state) render eff r + -> ReactClass (Record props) +pureComponent = pureComponentImpl + +foreign import componentImpl :: forall this props eff r. String -> - Eff (refs :: ReactRefs (read :: Read | access) | eff) (Nullable Ref) + (this -> Eff eff r) -> + ReactClass props --- | Read named ref from `Refs`. -readRef :: forall props state access eff. - ReactThis props state -> +foreign import pureComponentImpl :: forall this props eff r. String -> - Eff (refs :: ReactRefs (read :: Read | access) | eff) (Maybe Ref) -readRef this name = toMaybe <$> readRefImpl this name + (this -> Eff eff r) -> + ReactClass props --- | Write a `Ref` to `Refs` -foreign import writeRef :: forall props state access eff. +-- | React class for components. +foreign import data ReactClass :: Type -> Type + +-- | Read the component props. +foreign import getProps :: forall props state eff. ReactThis props state -> - String -> - Nullable Ref -> - Eff (refs :: ReactRefs (write :: Write | access) | eff) Unit + Eff (props :: ReactProps | eff) props -- | Read the component children property. foreign import getChildren :: forall props state eff. @@ -375,7 +326,10 @@ foreign import writeState :: forall props state access eff. Eff (state :: ReactState (write :: Write | access) | eff) state -- | Write the component state with a callback. -foreign import writeStateWithCallback :: forall props state access eff. ReactThis props state -> state -> Eff (state :: ReactState (write :: Write | access) | eff) Unit -> Eff (state :: ReactState (write :: Write | access) | eff) state +foreign import writeStateWithCallback :: forall props state access eff. + ReactThis props state -> + state -> + Eff (state :: ReactState (write :: Write | access) | eff) Unit -> Eff (state :: ReactState (write :: Write | access) | eff) state -- | Read the component state. foreign import readState :: forall props state access eff. @@ -388,37 +342,6 @@ foreign import transformState :: forall props state eff. (state -> state) -> Eff (state :: ReactState ReadWrite | eff) Unit --- | Create a React class from a specification. -foreign import createClass' :: forall props state render eff. - Fn2 - (forall a. Maybe a -> Nullable a) - (ReactSpec props state render eff) - (ReactClass props) - -createClass :: forall props state render eff. - ReactSpec props state render eff -> ReactClass props -createClass spc = runFn2 createClass' toNullable spc - --- | Create a stateless React class. When using a non anonymous function the --- | displayName will be the capitalized name of the function, e.g. --- | ``` purescript --- | helloWorld = createClassStatelesss helloWorldCls --- | where --- | helloWorldCls props = ... --- | ``` --- | Then the `displayName` will be set up to `HelloWorldCls` -foreign import createClassStateless :: forall props render. - ReactRender render => - (props -> render) -> ReactClass props - --- | Create a stateless React class with children access. -createClassStateless' :: forall props render. - ReactRender render => - (props -> Array ReactElement -> render) -> ReactClass props -createClassStateless' k = - createClassStateless \props -> - k props (childrenToArray (unsafeCoerce props).children) - -- | Force render of a react component. forceUpdate :: forall eff props state. ReactThis props state -> Eff eff Unit @@ -455,10 +378,6 @@ foreign import createElementTagName :: forall props. foreign import createElementTagNameDynamic :: forall props. TagName -> props -> Array ReactElement -> ReactElement --- | Create a factory from a React class. -foreign import createFactory :: forall props. - ReactClass props -> props -> ReactElement - -- | Internal representation for the children elements passed to a component foreign import data Children :: Type diff --git a/src/React/DOM/Props.purs b/src/React/DOM/Props.purs index 4af5349..8d0e829 100644 --- a/src/React/DOM/Props.purs +++ b/src/React/DOM/Props.purs @@ -1,10 +1,8 @@ module React.DOM.Props where -import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Unsafe (unsafePerformEff) +import Data.Foreign (Foreign) import Data.Nullable (Nullable) -import Prelude (Unit, (<<<)) -import React (Event, EventHandlerContext, KeyboardEvent, MouseEvent, ReactRefs, Ref, Write, handle) +import React (Event, EventHandlerContext, KeyboardEvent, MouseEvent, handle) foreign import data Props :: Type @@ -301,22 +299,6 @@ radioGroup = unsafeMkProps "radioGroup" readOnly :: Boolean -> Props readOnly = unsafeMkProps "readOnly" --- | You can use `ref` to store a reference on `this.refs`. --- | To access the stored reference, `getRefs` can then be used. -ref :: String -> Props -ref = unsafeMkProps "ref" - --- | You can use `writeRef` to store a reference on `this`. --- | ```purescript --- | div [ withRef (writeRef this "inputElement") ] [...] --- | ``` --- | To access the stored reference, `readRef` can then be used. -withRef - :: forall access eff - . (Nullable Ref -> Eff (refs :: ReactRefs (write :: Write | access) | eff) Unit) - -> Props -withRef cb = unsafeMkProps "ref" (unsafePerformEff <<< cb) - rel :: String -> Props rel = unsafeMkProps "rel" @@ -638,6 +620,10 @@ onWheel :: forall eff props state result. (Event -> EventHandlerContext eff props state result) -> Props onWheel f = unsafeMkProps "onWheel" (handle f) +ref :: forall eff props state result. + (Nullable Foreign -> EventHandlerContext eff props state result) -> Props +ref f = unsafeMkProps "ref" (handle f) + suppressContentEditableWarning :: Boolean -> Props suppressContentEditableWarning = unsafeMkProps "suppressContentEditableWarning" From 90ff19d335966ad775970abb610612188001abc8 Mon Sep 17 00:00:00 2001 From: Nathan Faubion Date: Thu, 30 Nov 2017 17:13:26 -0800 Subject: [PATCH 2/5] Revert Ref -> Foreign --- bower.json | 1 - src/React.purs | 5 +++++ src/React/DOM/Props.purs | 5 ++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index 8c77b09..ffb30d8 100644 --- a/bower.json +++ b/bower.json @@ -23,7 +23,6 @@ "purescript-exceptions": "^3.1.0", "purescript-maybe": "^3.0.0", "purescript-nullable": "^3.0.0", - "purescript-foreign": "^4.0.1", "purescript-typelevel-prelude": "^2.5.0" }, "devDependencies": { diff --git a/src/React.purs b/src/React.purs index 36deb59..c5c4f71 100644 --- a/src/React.purs +++ b/src/React.purs @@ -29,6 +29,7 @@ module React , ComponentWillUnmount , ReactClass + , Ref , Event , MouseEvent @@ -309,6 +310,10 @@ foreign import pureComponentImpl :: forall this props eff r. -- | React class for components. foreign import data ReactClass :: Type -> Type +-- | Type for React refs. This type is opaque, but you can use `Data.Foreign` +-- | and `DOM` to validate the underlying representation. +foreign import data Ref :: Type + -- | Read the component props. foreign import getProps :: forall props state eff. ReactThis props state -> diff --git a/src/React/DOM/Props.purs b/src/React/DOM/Props.purs index 8d0e829..6c554ee 100644 --- a/src/React/DOM/Props.purs +++ b/src/React/DOM/Props.purs @@ -1,8 +1,7 @@ module React.DOM.Props where -import Data.Foreign (Foreign) import Data.Nullable (Nullable) -import React (Event, EventHandlerContext, KeyboardEvent, MouseEvent, handle) +import React (Event, EventHandlerContext, KeyboardEvent, MouseEvent, Ref, handle) foreign import data Props :: Type @@ -621,7 +620,7 @@ onWheel :: forall eff props state result. onWheel f = unsafeMkProps "onWheel" (handle f) ref :: forall eff props state result. - (Nullable Foreign -> EventHandlerContext eff props state result) -> Props + (Nullable Ref -> EventHandlerContext eff props state result) -> Props ref f = unsafeMkProps "ref" (handle f) suppressContentEditableWarning :: Boolean -> Props From 18adc4bfa51e9803f4c1167613021d7f0eaad3e2 Mon Sep 17 00:00:00 2001 From: Nathan Faubion Date: Sun, 25 Feb 2018 09:05:36 -0800 Subject: [PATCH 3/5] Move prop constraints to createElement --- src/React.js | 33 +++++++++------------------ src/React.purs | 61 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/React.js b/src/React.js index 4445587..7a7e251 100644 --- a/src/React.js +++ b/src/React.js @@ -60,27 +60,9 @@ function getProps(this_) { } exports.getProps = getProps; -function childrenToArray(children) { - var result = []; +exports.childrenToArray = React.Children.toArray - React.Children.forEach(children, function(child){ - result.push(child); - }); - - return result; -} -exports.childrenToArray = childrenToArray; - -function getChildren(this_) { - return function(){ - var children = this_.props.children; - - var result = childrenToArray(children); - - return result; - }; -} -exports.getChildren = getChildren; +exports.childrenCount = React.Children.count; function writeState(this_) { return function(state){ @@ -144,9 +126,16 @@ function createElement(class_) { }; }; } -exports.createElement = createElement; +exports.createElementImpl = createElement; exports.createElementTagName = createElement; +function createLeafElement(class_) { + return function(props) { + return React.createElement(class_, props); + }; +} +exports.createLeafElementImpl = createLeafElement; + function createElementDynamic(class_) { return function(props) { return function(children){ @@ -154,7 +143,7 @@ function createElementDynamic(class_) { }; }; }; -exports.createElementDynamic = createElementDynamic; +exports.createElementDynamicImpl = createElementDynamic; exports.createElementTagNameDynamic = createElementDynamic; function preventDefault(event) { diff --git a/src/React.purs b/src/React.purs index c5c4f71..67060dd 100644 --- a/src/React.purs +++ b/src/React.purs @@ -41,7 +41,6 @@ module React , pureComponent , getProps - , getChildren , readState , writeState @@ -59,9 +58,13 @@ module React , createElementDynamic , createElementTagName , createElementTagNameDynamic + , createLeafElement , Children , childrenToArray + , childrenCount + + , class ReactPropFields ) where import Prelude @@ -69,7 +72,6 @@ import Prelude import Control.Monad.Eff (kind Effect, Eff) import Control.Monad.Eff.Exception (Error) import Control.Monad.Eff.Uncurried (EffFn2, runEffFn2) -import Type.Row (class RowLacks) -- | Name of a tag. type TagName = String @@ -277,8 +279,6 @@ type ReactClassConstructor props state render eff r = component :: forall props state render eff r x . Union (ReactSpecRequired (Record state) render eff r) x (ReactSpecAll (Record props) (Record state) render eff) - => RowLacks "children" props - => RowLacks "key" props => ReactRender render => String -> ReactClassConstructor (Record props) (Record state) render eff r @@ -289,8 +289,6 @@ component = componentImpl pureComponent :: forall props state render eff r x . Union (ReactSpecRequired (Record state) render eff r) x (ReactSpecPure (Record props) (Record state) render eff) - => RowLacks "children" props - => RowLacks "key" props => ReactRender render => String -> ReactClassConstructor (Record props) (Record state) render eff r @@ -319,11 +317,6 @@ foreign import getProps :: forall props state eff. ReactThis props state -> Eff (props :: ReactProps | eff) props --- | Read the component children property. -foreign import getChildren :: forall props state eff. - ReactThis props state -> - Eff (props :: ReactProps | eff) (Array ReactElement) - -- | Write the component state. foreign import writeState :: forall props state access eff. ReactThis props state -> @@ -367,13 +360,48 @@ forceUpdateCb this m = runEffFn2 forceUpdateCbImpl this m foreign import handle :: forall eff ev props state result. (ev -> EventHandlerContext eff props state result) -> EventHandler ev +class ReactPropFields (required :: # Type) (given :: # Type) + +instance reactPropFields :: + ( Union given optional (key :: String | required) + , Union optional leftover (key :: String) + ) => + ReactPropFields required given + -- | Create an element from a React class spreading the children array. Used when the children are known up front. -foreign import createElement :: forall props. - ReactClass props -> props -> Array ReactElement -> ReactElement +createElement :: forall required given. + ReactPropFields required given => + ReactClass { children :: Children | required } -> + { | given } -> + Array ReactElement -> + ReactElement +createElement = createElementImpl -- | Create an element from a React class passing the children array. Used for a dynamic array of children. -foreign import createElementDynamic :: forall props. - ReactClass props -> props -> Array ReactElement -> ReactElement +createElementDynamic :: forall required given. + ReactPropFields required given => + ReactClass { children :: Children | required } -> + { | given } -> + Array ReactElement -> + ReactElement +createElementDynamic = createElementDynamicImpl + +foreign import createElementImpl :: forall required given children. + ReactClass required -> given -> Array children -> ReactElement + +foreign import createElementDynamicImpl :: forall required given children. + ReactClass required -> given -> Array children -> ReactElement + +-- | Create an element from a React class that does not require children. +createLeafElement :: forall required given. + ReactPropFields required given => + ReactClass { | required } -> + { | given } -> + ReactElement +createLeafElement = createLeafElementImpl + +foreign import createLeafElementImpl :: forall required given. + ReactClass required -> given -> ReactElement -- | Create an element from a tag name spreading the children array. Used when the children are known up front. foreign import createElementTagName :: forall props. @@ -389,6 +417,9 @@ foreign import data Children :: Type -- | Internal conversion function from children elements to an array of React elements foreign import childrenToArray :: Children -> Array ReactElement +-- | Returns the number of children. +foreign import childrenCount :: Children -> Int + foreign import preventDefault :: forall eff. Event -> Eff eff Unit foreign import stopPropagation :: forall eff. Event -> Eff eff Unit From c7c39cc8dd7c7d395641a1cc1bff554069c5a7f8 Mon Sep 17 00:00:00 2001 From: Nathan Faubion Date: Sat, 3 Mar 2018 10:10:48 -0800 Subject: [PATCH 4/5] Add ref as a reserved prop --- src/React.purs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/React.purs b/src/React.purs index 67060dd..9d73fcd 100644 --- a/src/React.purs +++ b/src/React.purs @@ -72,6 +72,7 @@ import Prelude import Control.Monad.Eff (kind Effect, Eff) import Control.Monad.Eff.Exception (Error) import Control.Monad.Eff.Uncurried (EffFn2, runEffFn2) +import Data.Nullable (Nullable) -- | Name of a tag. type TagName = String @@ -362,9 +363,15 @@ foreign import handle :: forall eff ev props state result. class ReactPropFields (required :: # Type) (given :: # Type) +type ReservedReactPropFields r = + ( key :: String + , ref :: EventHandler (Nullable Ref) + | r + ) + instance reactPropFields :: - ( Union given optional (key :: String | required) - , Union optional leftover (key :: String) + ( Union given optional (ReservedReactPropFields required) + , Union optional leftover (ReservedReactPropFields ()) ) => ReactPropFields required given From d7233a0598dd0d5544efc9c99058ed3288df77e7 Mon Sep 17 00:00:00 2001 From: Nathan Faubion Date: Sat, 3 Mar 2018 10:26:18 -0800 Subject: [PATCH 5/5] Add statelessComponent --- src/React.js | 2 ++ src/React.purs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/React.js b/src/React.js index 7a7e251..b1fec0b 100644 --- a/src/React.js +++ b/src/React.js @@ -53,6 +53,8 @@ exports.componentImpl = createClass(React.Component); exports.pureComponentImpl = createClass(React.PureComponent); +exports.statelessComponent = function(x) { return x; }; + function getProps(this_) { return function(){ return this_.props; diff --git a/src/React.purs b/src/React.purs index 9d73fcd..e13d878 100644 --- a/src/React.purs +++ b/src/React.purs @@ -39,6 +39,7 @@ module React , component , pureComponent + , statelessComponent , getProps @@ -306,6 +307,10 @@ foreign import pureComponentImpl :: forall this props eff r. (this -> Eff eff r) -> ReactClass props +foreign import statelessComponent :: forall props. + (Record props -> ReactElement) -> + ReactClass (Record props) + -- | React class for components. foreign import data ReactClass :: Type -> Type