From d40597cab3426d8207881bcceb8632c485aa1d0c Mon Sep 17 00:00:00 2001 From: Phil Freeman Date: Wed, 21 Feb 2018 11:15:10 -0800 Subject: [PATCH] Add setup function, to wrap componentDidMount (#10) * Add setup function, to wrap componentDidMount * Bind this_ * Counter example (no webpack) * Update for new setup function * Add component function, and example * Rename to receiveProps, remove props from initialState * Update readme * Remove package-lock.json --- README.md | 5 ++-- examples/component/.gitignore | 4 +++ examples/component/Makefile | 5 ++++ examples/component/README.md | 12 +++++++++ examples/component/html/index.html | 10 ++++++++ examples/component/index.js | 10 ++++++++ examples/component/package.json | 15 ++++++++++++ examples/component/src/Container.purs | 16 ++++++++++++ examples/component/src/ToggleButton.purs | 24 ++++++++++++++++++ examples/counter/.gitignore | 4 +++ examples/counter/Makefile | 5 ++++ examples/counter/README.md | 12 +++++++++ examples/counter/html/index.html | 10 ++++++++ examples/counter/index.js | 10 ++++++++ examples/counter/package.json | 15 ++++++++++++ examples/counter/src/Counter.purs | 30 +++++++++++++++++++++++ generated-docs/React/Basic.md | 14 ++++++++--- src/React/Basic.js | 22 ++++++++++++++++- src/React/Basic.purs | 31 +++++++++++++++++++----- 19 files changed, 242 insertions(+), 12 deletions(-) create mode 100644 examples/component/.gitignore create mode 100644 examples/component/Makefile create mode 100644 examples/component/README.md create mode 100644 examples/component/html/index.html create mode 100644 examples/component/index.js create mode 100644 examples/component/package.json create mode 100644 examples/component/src/Container.purs create mode 100644 examples/component/src/ToggleButton.purs create mode 100644 examples/counter/.gitignore create mode 100644 examples/counter/Makefile create mode 100644 examples/counter/README.md create mode 100644 examples/counter/html/index.html create mode 100644 examples/counter/index.js create mode 100644 examples/counter/package.json create mode 100644 examples/counter/src/Counter.purs diff --git a/README.md b/README.md index 2d0d398..565a6ac 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This package implements an opinionated set of bindings to the React library, optimizing for the most basic use cases. -## Features +## Features - All React DOM elements and attributes are supported. - An intuitive API for specifying props - no arrays of key value pairs, just records. @@ -41,7 +41,8 @@ type ExampleState = -- state update callback, and produces a document. example :: R.ReactComponent ExampleProps example = R.react - { initialState: \_ -> { counter: 0 } + { initialState: { counter: 0 } + , receiveProps: \_ _ _ -> pure unit , render: \{ label } { counter } setState -> R.button { onClick: mkEffFn1 \_ -> do setState { counter: counter + 1 } diff --git a/examples/component/.gitignore b/examples/component/.gitignore new file mode 100644 index 0000000..645684d --- /dev/null +++ b/examples/component/.gitignore @@ -0,0 +1,4 @@ +output +html/index.js +package-lock.json +node_modules diff --git a/examples/component/Makefile b/examples/component/Makefile new file mode 100644 index 0000000..a7679c9 --- /dev/null +++ b/examples/component/Makefile @@ -0,0 +1,5 @@ +all: + purs compile src/*.purs '../../src/**/*.purs' '../../bower_components/purescript-*/src/**/*.purs' + purs bundle --module Container output/*/*.js > output/bundle.js + echo 'module.exports = PS.Container;' >> output/bundle.js + node_modules/browserify/bin/cmd.js output/bundle.js index.js -o html/index.js diff --git a/examples/component/README.md b/examples/component/README.md new file mode 100644 index 0000000..cc747f4 --- /dev/null +++ b/examples/component/README.md @@ -0,0 +1,12 @@ +# Component 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/component/html/index.html b/examples/component/html/index.html new file mode 100644 index 0000000..6b93b7c --- /dev/null +++ b/examples/component/html/index.html @@ -0,0 +1,10 @@ + + + + react-basic example + + +
+ + + diff --git a/examples/component/index.js b/examples/component/index.js new file mode 100644 index 0000000..1a54c75 --- /dev/null +++ b/examples/component/index.js @@ -0,0 +1,10 @@ +"use strict"; + +var React = require("react"); +var ReactDOM = require("react-dom"); +var Container = require("./output/bundle.js"); + +ReactDOM.render( + React.createElement(Container.component, { label: 'Increment' }), + document.getElementById("container") +); diff --git a/examples/component/package.json b/examples/component/package.json new file mode 100644 index 0000000..8648b2b --- /dev/null +++ b/examples/component/package.json @@ -0,0 +1,15 @@ +{ + "name": "component", + "version": "1.0.0", + "description": "", + "keywords": [], + "author": "", + "dependencies": { + "create-react-class": "^15.6.2", + "react": "^15.6.2", + "react-dom": "^15.6.2" + }, + "devDependencies": { + "browserify": "^16.1.0" + } +} diff --git a/examples/component/src/Container.purs b/examples/component/src/Container.purs new file mode 100644 index 0000000..8ce3150 --- /dev/null +++ b/examples/component/src/Container.purs @@ -0,0 +1,16 @@ +module Container where + +import Prelude + +import React.Basic as R +import ToggleButton as ToggleButton + +component :: R.ReactComponent Unit +component = R.react + { initialState: unit + , receiveProps: \_ _ _ -> pure unit + , render: \_ _ setState -> + R.div { } [ R.component ToggleButton.component { on: true } + , R.component ToggleButton.component { on: false } + ] + } diff --git a/examples/component/src/ToggleButton.purs b/examples/component/src/ToggleButton.purs new file mode 100644 index 0000000..fd11c6e --- /dev/null +++ b/examples/component/src/ToggleButton.purs @@ -0,0 +1,24 @@ +module ToggleButton where + +import Prelude + +import Control.Monad.Eff.Uncurried (mkEffFn1) +import React.Basic as R + +type ExampleProps = + { on :: Boolean + } + +type ExampleState = + { on :: Boolean + } + +component :: R.ReactComponent ExampleProps +component = R.react + { initialState: { on: false } + , receiveProps: \{ on } _ setState -> setState { on } + , render: \_ { on } setState -> + R.button { onClick: mkEffFn1 \_ -> setState { on: not on } + } + [ R.text if on then "On" else "Off" ] + } diff --git a/examples/counter/.gitignore b/examples/counter/.gitignore new file mode 100644 index 0000000..645684d --- /dev/null +++ b/examples/counter/.gitignore @@ -0,0 +1,4 @@ +output +html/index.js +package-lock.json +node_modules diff --git a/examples/counter/Makefile b/examples/counter/Makefile new file mode 100644 index 0000000..a31aa7b --- /dev/null +++ b/examples/counter/Makefile @@ -0,0 +1,5 @@ +all: + purs compile src/*.purs '../../src/**/*.purs' '../../bower_components/purescript-*/src/**/*.purs' + purs bundle --module Counter output/*/*.js > output/bundle.js + echo 'module.exports = PS.Counter;' >> output/bundle.js + node_modules/browserify/bin/cmd.js output/bundle.js index.js -o html/index.js diff --git a/examples/counter/README.md b/examples/counter/README.md new file mode 100644 index 0000000..f2418c0 --- /dev/null +++ b/examples/counter/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/counter/html/index.html b/examples/counter/html/index.html new file mode 100644 index 0000000..6b93b7c --- /dev/null +++ b/examples/counter/html/index.html @@ -0,0 +1,10 @@ + + + + react-basic example + + +
+ + + diff --git a/examples/counter/index.js b/examples/counter/index.js new file mode 100644 index 0000000..4fbcf78 --- /dev/null +++ b/examples/counter/index.js @@ -0,0 +1,10 @@ +"use strict"; + +var React = require("react"); +var ReactDOM = require("react-dom"); +var Counter = require("./output/bundle.js"); + +ReactDOM.render( + React.createElement(Counter.component, { label: 'Increment' }), + document.getElementById("container") +); diff --git a/examples/counter/package.json b/examples/counter/package.json new file mode 100644 index 0000000..b1ac6d8 --- /dev/null +++ b/examples/counter/package.json @@ -0,0 +1,15 @@ +{ + "name": "counter", + "version": "1.0.0", + "description": "", + "keywords": [], + "author": "", + "dependencies": { + "create-react-class": "^15.6.2", + "react": "^15.6.2", + "react-dom": "^15.6.2" + }, + "devDependencies": { + "browserify": "^16.1.0" + } +} diff --git a/examples/counter/src/Counter.purs b/examples/counter/src/Counter.purs new file mode 100644 index 0000000..724d4d6 --- /dev/null +++ b/examples/counter/src/Counter.purs @@ -0,0 +1,30 @@ +module Counter where + +import Prelude + +import Control.Monad.Eff.Uncurried (mkEffFn1) +import React.Basic as R + +-- The props for the component +type ExampleProps = + { label :: String + } + +-- The internal state of the component +type ExampleState = + { counter :: Int + } + +-- 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 :: R.ReactComponent ExampleProps +component = R.react + { initialState: { counter: 0 } + , receiveProps: \_ _ _ -> pure unit + , render: \{ label } { counter } setState -> + R.button { onClick: mkEffFn1 \_ -> do + setState { counter: counter + 1 } + } + [ R.text (label <> ": " <> show counter) ] + } diff --git a/generated-docs/React/Basic.md b/generated-docs/React/Basic.md index 7b1ec97..bebdf3e 100644 --- a/generated-docs/React/Basic.md +++ b/generated-docs/React/Basic.md @@ -3,19 +3,27 @@ #### `react` ``` purescript -react :: forall props state. { initialState :: state, render :: props -> state -> (state -> Eff (react :: ReactFX) Unit) -> JSX } -> ReactComponent props +react :: forall props state. { initialState :: state, receiveProps :: props -> state -> (state -> Eff (react :: ReactFX) Unit) -> Eff (react :: ReactFX) Unit, render :: props -> state -> (state -> Eff (react :: ReactFX) Unit) -> JSX } -> ReactComponent props ``` Create a React component from a _specification_ of that component. A _specification_ consists of a state type, an initial value for that state, -and a rendering function which takes a value of that state type, additional -_props_ (which will be passed in by the user) and a state update function. +a function to apply incoming props to the internal state, and a rendering +function which takes props, state and a state update function. 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). +#### `component` + +``` purescript +component :: forall props. ReactComponent props -> props -> JSX +``` + +Create a `JSX` node from another React component, by providing the props. + ### Re-exported from React.Basic.DOM: diff --git a/src/React/Basic.js b/src/React/Basic.js index 02727ca..d5b68bc 100644 --- a/src/React/Basic.js +++ b/src/React/Basic.js @@ -5,7 +5,23 @@ var React = require('react'); exports.react_ = function(spec) { return React.createClass({ getInitialState: function() { - return spec.initialState(this.props); + return spec.initialState; + }, + componentDidMount: function() { + var this_ = this; + spec.receiveProps(this.props, this.state, function(newState) { + return function() { + this_.setState(newState); + }; + }); + }, + componentWillReceiveProps: function(newProps) { + var this_ = this; + spec.receiveProps(newProps, this.state, function(newState) { + return function() { + this_.setState(newState); + }; + }); }, render: function() { var this_ = this; @@ -17,3 +33,7 @@ exports.react_ = function(spec) { } }); }; + +exports.component_ = function(component, props) { + return React.createElement(component, props); +} diff --git a/src/React/Basic.purs b/src/React/Basic.purs index e12e2d0..0ee2cdd 100644 --- a/src/React/Basic.purs +++ b/src/React/Basic.purs @@ -1,5 +1,6 @@ module React.Basic ( react + , component , module React.Basic.DOM , module React.Basic.Types ) where @@ -7,14 +8,16 @@ module React.Basic import Prelude import Control.Monad.Eff (Eff, kind Effect) -import Data.Function.Uncurried (Fn3, mkFn3) +import Control.Monad.Eff.Uncurried (EffFn3, mkEffFn3) +import Data.Function.Uncurried (Fn2, runFn2, Fn3, mkFn3) import React.Basic.DOM as React.Basic.DOM import React.Basic.Types (CSS, EventHandler, JSX, ReactComponent, ReactFX) import React.Basic.Types as React.Basic.Types foreign import react_ :: forall props state - . { initialState :: props -> state + . { initialState :: state + , receiveProps :: EffFn3 (react :: ReactFX) props state (state -> Eff (react :: ReactFX) Unit) Unit , render :: Fn3 props state (state -> Eff (react :: ReactFX) Unit) JSX } -> ReactComponent props @@ -22,16 +25,32 @@ foreign import react_ -- | Create a React component from a _specification_ of that component. -- | -- | A _specification_ consists of a state type, an initial value for that state, --- | and a rendering function which takes a value of that state type, additional --- | _props_ (which will be passed in by the user) and a state update function. +-- | a function to apply incoming props to the internal state, and a rendering +-- | function which takes props, state and a state update function. -- | -- | 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 - . { initialState :: props -> state + . { initialState :: state + , receiveProps :: props -> state -> (state -> Eff (react :: ReactFX) Unit) -> Eff (react :: ReactFX) Unit , render :: props -> state -> (state -> Eff (react :: ReactFX) Unit) -> JSX } -> ReactComponent props -react { initialState, render } = react_ { initialState, render: mkFn3 render } +react { initialState, receiveProps, render } = + react_ + { initialState + , receiveProps: mkEffFn3 receiveProps + , render: mkFn3 render + } + +foreign import component_ :: forall props. Fn2 (ReactComponent props) props JSX + +-- | Create a `JSX` node from another React component, by providing the props. +component + :: forall props + . ReactComponent props + -> props + -> JSX +component = runFn2 component_