diff --git a/bower.json b/bower.json index 73374eb..690f7b3 100644 --- a/bower.json +++ b/bower.json @@ -20,6 +20,7 @@ "purescript-eff": "^3.0.0", "purescript-prelude": "^3.0.0", "purescript-unsafe-coerce": "^3.0.0", + "purescript-exceptions": "^3.1.0", "purescript-maybe": "^3.0.0", "purescript-nullable": "^3.0.0" }, diff --git a/package.json b/package.json index 1e1b0fc..a9d0e9b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "purescript-react", "files": [], "peerDependencies": { - "react": "^15.6.1", + "react": "^16.0.0-alpha.13", "create-react-class": "^15.6.0" } } diff --git a/src/React.js b/src/React.js index 451f8dd..999620d 100644 --- a/src/React.js +++ b/src/React.js @@ -108,7 +108,9 @@ function transformState(this_){ } exports.transformState = transformState; -function createClass(spec) { +function createClass(toNullable, spec) { + var didCatch = toNullable(spec.componentDidCatch) + var result = { displayName: spec.displayName, render: function(){ @@ -125,6 +127,9 @@ function createClass(spec) { 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)(); }, @@ -144,7 +149,22 @@ function createClass(spec) { return createReactClass(result); } -exports.createClass = createClass; +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() { diff --git a/src/React.purs b/src/React.purs index 9a4f927..893d409 100644 --- a/src/React.purs +++ b/src/React.purs @@ -1,7 +1,8 @@ -- | This module defines foreign types and functions which wrap React's functionality. module React - ( ReactElement + ( class ReactRender + , ReactElement , ReactComponent , ReactThis , TagName @@ -25,6 +26,7 @@ module React , GetInitialState , ComponentWillMount , ComponentDidMount + , ComponentDidCatch , ComponentWillReceiveProps , ShouldComponentUpdate , ComponentWillUpdate @@ -76,8 +78,10 @@ module React import Prelude import Control.Monad.Eff (kind Effect, Eff) -import Data.Maybe (Maybe) -import Data.Nullable (Nullable, toMaybe) +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) @@ -160,15 +164,27 @@ type EventHandlerContext eff props state result = | eff ) result +class ReactRender a + +instance arrayReactRender :: ReactRender (Array ReactElement) + +instance reactElementReactRender :: ReactRender ReactElement + +instance stringReactRender :: ReactRender String + +instance intReactRender :: ReactRender Int + +instance numberReactRender :: ReactRender Number + -- | A render function. -type Render props state eff = +type Render props state render eff = ReactThis props state -> Eff ( props :: ReactProps , refs :: ReactRefs Disallowed , state :: ReactState ReadOnly | eff - ) ReactElement + ) render -- | A get initial state function. type GetInitialState props state eff = @@ -200,6 +216,18 @@ type ComponentDidMount props state eff = | eff ) Unit +type ComponentDidCatch props state eff = + ReactThis props state -> + 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 -> @@ -258,12 +286,13 @@ type ComponentWillUnmount props state eff = ) Unit -- | A specification of a component. -type ReactSpec props state eff = - { render :: Render props state eff +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 , componentWillUpdate :: ComponentWillUpdate props state eff @@ -272,21 +301,24 @@ type ReactSpec props state eff = } -- | Create a component specification with a provided state. -spec :: forall props state eff. - state -> Render props state eff -> ReactSpec props state eff +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 eff. +spec' :: forall props state render eff. + ReactRender render => GetInitialState props state eff -> - Render props state eff -> - ReactSpec 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 @@ -357,17 +389,32 @@ foreign import transformState :: forall props state eff. Eff (state :: ReactState ReadWrite | eff) Unit -- | Create a React class from a specification. -foreign import createClass :: forall props state eff. - ReactSpec props state eff -> ReactClass props - --- | Create a stateless React class. -createClassStateless :: forall props. - (props -> ReactElement) -> ReactClass props -createClassStateless = unsafeCoerce +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 hellowWorldCls +-- | where +-- | hellowWorldCls props = ... +-- | ``` +-- | Then the `displayName` will be set up to `HellowWorldCls` +foreign import createClassStateless :: forall props render. + ReactRender render => + (props -> render) -> ReactClass props -- | Create a stateless React class with children access. -createClassStateless' :: forall props. - (props -> Array ReactElement -> ReactElement) -> ReactClass props +createClassStateless' :: forall props render. + ReactRender render => + (props -> Array ReactElement -> render) -> ReactClass props createClassStateless' k = createClassStateless \props -> k props (childrenToArray (unsafeCoerce props).children) diff --git a/src/React/DOM.purs b/src/React/DOM.purs index fbb5c18..079cce6 100644 --- a/src/React/DOM.purs +++ b/src/React/DOM.purs @@ -19,6 +19,12 @@ mkDOM dynamic tag props = createElement tag (unsafeFromPropsArray props) text :: String -> ReactElement text = unsafeCoerce +int :: Int -> ReactElement +int = unsafeCoerce + +number :: Number -> ReactElement +number = unsafeCoerce + a :: Array Props -> Array ReactElement -> ReactElement a = mkDOM (IsDynamic false) "a"