Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Refactor #44

Merged
merged 10 commits into from
Aug 13, 2018
Next Next commit
new hotness
  • Loading branch information
megamaddu committed Jul 11, 2018
commit 683ff304e4452d9c0b5249ffd8ecbcc337469f45
6 changes: 3 additions & 3 deletions examples/component/src/Container.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ module Container where

import React.Basic (ReactComponent, createElement, stateless)
import React.Basic.DOM as R
import ToggleButton as ToggleButton
import ToggleButton (toggleButton)

component :: ReactComponent {}
component = stateless { displayName: "Container", render }
where
render _ =
R.div
{ children:
[ createElement ToggleButton.component { on: true }
, createElement ToggleButton.component { on: false }
[ createElement toggleButton { on: true }
, createElement toggleButton { on: false }
]
}
10 changes: 5 additions & 5 deletions examples/component/src/ToggleButton.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ module ToggleButton where

import Prelude

import React.Basic (ReactComponent, react)
import React.Basic (ReactComponent, component)
import React.Basic.DOM as R
import React.Basic.Events as Events

type ExampleProps =
{ on :: Boolean
}

component :: ReactComponent ExampleProps
component = react { displayName: "ToggleButton", initialState, receiveProps, render }
toggleButton :: ReactComponent ExampleProps
toggleButton = component { displayName: "ToggleButton", initialState, receiveProps, render }
where
initialState =
{ on: false }

receiveProps props _ setState =
receiveProps { props, setState } =
setState _ { on = props.on }

render _ state setState =
render { state, setState } =
R.button
{ onClick: Events.handler_ (setState \s -> s { on = not s.on })
, children: [ R.text if state.on then "On" else "Off" ]
Expand Down
2 changes: 1 addition & 1 deletion examples/controlled-input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ var ReactDOM = require("react-dom");
var ControlledInput = require("./output/bundle.js");

ReactDOM.render(
React.createElement(ControlledInput.component),
React.createElement(ControlledInput.controlledInput),
document.getElementById("container")
);
12 changes: 6 additions & 6 deletions examples/controlled-input/src/ControlledInput.purs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ module ControlledInput where
import Prelude

import Data.Maybe (Maybe(..), fromMaybe, maybe)
import React.Basic (ReactComponent, fragment, react)
import React.Basic (ReactComponent, component, fragment)
import React.Basic.DOM as R
import React.Basic.DOM.Events (preventDefault, targetValue, timeStamp)
import React.Basic.Events as Events

component :: ReactComponent {}
component = react { displayName: "ControlledInput", initialState, receiveProps, render }
controlledInput :: ReactComponent {}
controlledInput = component { displayName: "ControlledInput", initialState, receiveProps, render }
where
initialState =
{ value: "hello world"
, timeStamp: Nothing
}

receiveProps _ _ _ =
receiveProps _ =
pure unit
render _ state setState =

render { state, setState } =
fragment
[ R.input
{ onChange:
Expand Down
2 changes: 1 addition & 1 deletion examples/counter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ var ReactDOM = require("react-dom");
var Counter = require("./output/bundle.js");

ReactDOM.render(
React.createElement(Counter.component, { label: 'Increment' }),
React.createElement(Counter.counter, { label: 'Increment' }),
document.getElementById("container")
);
10 changes: 5 additions & 5 deletions examples/counter/src/Counter.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Counter where

import Prelude

import React.Basic (ReactComponent, react)
import React.Basic (ReactComponent, component)
import React.Basic.DOM as R
import React.Basic.Events as Events

Expand All @@ -14,17 +14,17 @@ type ExampleProps =
-- Create a component by passing a record to the `react` function.
-- The `render` function takes the props and current state, as well as a
-- state update callback, and produces a document.
component :: ReactComponent ExampleProps
component = react { displayName: "Counter", initialState, receiveProps, render }
counter :: ReactComponent ExampleProps
counter = component { displayName: "Counter", initialState, receiveProps, render }
where
initialState =
{ counter: 0
}

receiveProps _ _ _ =
receiveProps _ =
pure unit

render props state setState =
render { props, state, setState } =
R.button
{ onClick: Events.handler_ do
setState \s -> s { counter = s.counter + 1 }
Expand Down
38 changes: 22 additions & 16 deletions src/React/Basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var Fragment = React.Fragment || "div";
exports.component_ = function(spec) {
var Component = function constructor(props) {
this.state = spec.initialState;
this._setState = this.setState.bind(this);
return this;
};

Expand All @@ -14,31 +15,36 @@ exports.component_ = function(spec) {
Component.displayName = spec.displayName;

Component.prototype.componentDidMount = function componentDidMount() {
var this_ = this;
spec.receiveProps(this.props, this.state, function(newState) {
return function() {
this_.setState(newState);
};
spec.receiveProps({
isFirstMount: true,
props: this.props,
state: this.state,
setState: this._setState,
setStateThen: this._setState,
instance_: this,
});
};

Component.prototype.componentWillReceiveProps = function componentWillReceiveProps(
newProps
) {
var this_ = this;
spec.receiveProps(newProps, this.state, function(newState) {
return function() {
this_.setState(newState);
};
spec.receiveProps({
isFirstMount: false,
props: newProps,
state: this.state,
setState: this._setState,
setStateThen: this._setState,
instance_: this,
});
};

Component.prototype.render = function render() {
var this_ = this;
return spec.render(this.props, this.state, function(newState) {
return function() {
this_.setState(newState);
};
return spec.render({
props: this.props,
state: this.state,
setState: this._setState,
setStateThen: this._setState,
instance_: this,
});
};

Expand All @@ -52,7 +58,7 @@ exports.createElement_ = function(el, attrs) {
);
};

exports.createElementKeyed_ = React.createElement;
exports.createElementKeyed_ = exports.createElement_;

exports.fragment = function(children) {
return React.createElement.apply(null, [Fragment, {}].concat(children));
Expand Down
101 changes: 83 additions & 18 deletions src/React/Basic.purs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module React.Basic
( react
( component
, stateless
, createElement
, createElementKeyed
Expand All @@ -8,13 +8,15 @@ module React.Basic
, fragmentKeyed
, JSX
, ReactComponent
, ReactComponentInstance
, react
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about the import story here. The ideal for me would be if qualified imports were assumed, so the code could be written as:

module MyComponent (component, State, Props) where

import React.Basic as React

type State = {}
type Props = {}

component :: React.Component Props
component = React.component {..}

I find the current naming a bit inconsistent (some names are prefixed with React and some aren't). What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems fine to me, we use that a bit as well. Pretty sure this already needs to be a major version bump anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it could also be added with a backward compatible synonym (type ReactComponent = Component), same as react -> component.

) where

import Prelude

import Data.Function.Uncurried (Fn2, Fn3, mkFn3, runFn2)
import Data.Function.Uncurried (Fn1, Fn2, mkFn1, runFn2)
import Effect (Effect)
import Effect.Uncurried (EffectFn3, mkEffectFn3)
import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, runEffectFn1, runEffectFn2)
import Unsafe.Coerce (unsafeCoerce)

-- | A virtual DOM element.
Expand All @@ -38,20 +40,48 @@ foreign import data ReactComponent :: Type -> Type
-- | The rendering function should return a value of type `JSX`, which can be
-- | constructed using the helper functions provided by the `React.Basic.DOM`
-- | module (and re-exported here).
react
:: forall props state fx
component
:: forall props state
. { displayName :: String
, initialState :: { | state }
, receiveProps :: { | props } -> { | state } -> (SetState state fx) -> Effect Unit
, render :: { | props } -> { | state } -> (SetState state fx) -> JSX
, receiveProps ::
{ isFirstMount :: Boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth pulling out a type synonym here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah.. I couldn't decide. It's only repeated once and it seemed harder to read that way, plus naming the alias

, props :: { | props }
, state :: { | state }
, setState :: ({ | state } -> { | state }) -> Effect Unit
, setStateThen :: ({ | state } -> { | state }) -> ({ | state } -> Effect Unit) -> Effect Unit
, instance_ :: ReactComponentInstance
}
-> Effect Unit
, render ::
{ props :: { | props }
, state :: { | state }
, setState :: ({ | state } -> { | state }) -> Effect Unit
, setStateThen :: ({ | state } -> { | state }) -> ({ | state } -> Effect Unit) -> Effect Unit
, instance_ :: ReactComponentInstance
}
-> JSX
}
-> ReactComponent { | props }
react { displayName, initialState, receiveProps, render } =
component { displayName, initialState, receiveProps, render } =
component_
{ displayName
, initialState
, receiveProps: mkEffectFn3 receiveProps
, render: mkFn3 render
, receiveProps: mkEffectFn1 \this -> receiveProps
{ isFirstMount: this.isFirstMount
, props: this.props
, state: this.state
, setState: runEffectFn1 this.setState
, setStateThen: \update cb -> runEffectFn2 this.setStateThen update (mkEffectFn1 cb)
, instance_: this.instance_
}
, render: mkFn1 \this -> render
{ props: this.props
, state: this.state
, setState: runEffectFn1 this.setState
, setStateThen: \update cb -> runEffectFn2 this.setStateThen update (mkEffectFn1 cb)
, instance_: this.instance_
}
}

-- | Create a stateless React component.
Expand All @@ -65,15 +95,15 @@ stateless
}
-> ReactComponent { | props }
stateless { displayName, render } =
react
component
{ displayName
, initialState: {}
, receiveProps: \_ _ _ -> pure unit
, render: \props _ _ -> render props
, receiveProps: \_ -> pure unit
, render: \this -> render this.props
}

-- | SetState uses an update function to modify the current state.
type SetState state fx = ({ | state } -> { | state }) -> Effect Unit
-- | Represents the mounted component instance, or "this" in vanilla React.
foreign import data ReactComponentInstance :: Type

-- | Create a `JSX` node from a React component, by providing the props.
createElement
Expand Down Expand Up @@ -111,11 +141,28 @@ fragmentKeyed = runFn2 fragmentKeyed_
-- | Private FFI

foreign import component_
:: forall props state fx
:: forall props state
. { displayName :: String
, initialState :: { | state }
, receiveProps :: EffectFn3 { | props } { | state } (SetState state fx) Unit
, render :: Fn3 { | props } { | state } (SetState state fx) JSX
, receiveProps ::
EffectFn1
{ isFirstMount :: Boolean
, props :: { | props }
, state :: { | state }
, setState :: EffectFn1 ({ | state } -> { | state }) Unit
, setStateThen :: EffectFn2 ({ | state } -> { | state }) (EffectFn1 { | state } Unit) Unit
, instance_ :: ReactComponentInstance
}
Unit
, render ::
Fn1
{ props :: { | props }
, state :: { | state }
, setState :: EffectFn1 ({ | state } -> { | state }) Unit
, setStateThen :: EffectFn2 ({ | state } -> { | state }) (EffectFn1 { | state } Unit) Unit
, instance_ :: ReactComponentInstance
}
JSX
}
-> ReactComponent { | props }

Expand All @@ -124,3 +171,21 @@ foreign import createElement_ :: forall props. Fn2 (ReactComponent { | props })
foreign import createElementKeyed_ :: forall props. Fn2 (ReactComponent { | props }) { key :: String | props } JSX

foreign import fragmentKeyed_ :: Fn2 String (Array JSX) JSX

-- | Deprecated -- prefer `component`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use Warn for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to just remove it, if updating requires changes anyway

-- | This function is only to make upgrades a little easier.
react
:: forall props state
. { displayName :: String
, initialState :: { | state }
, receiveProps :: { | props } -> { | state } -> (({ | state } -> { | state }) -> Effect Unit) -> Effect Unit
, render :: { | props } -> { | state } -> (({ | state } -> { | state }) -> Effect Unit) -> JSX
}
-> ReactComponent { | props }
react { displayName, initialState, receiveProps, render } =
component
{ displayName
, initialState
, receiveProps: \this -> receiveProps this.props this.state this.setState
, render: \this -> render this.props this.state this.setState
}