Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3345f8e
api experiment
Aug 31, 2018
e8bf0dc
more reason-react-like api experiment
Oct 4, 2018
5cf3975
Merge branch 'master' of github.com:lumihq/purescript-react-basic int…
Oct 5, 2018
5161ed1
Add Compat module; fix stateless; build docs
Oct 6, 2018
704efe9
Split the spec component type from ReactComponent; fix toReactComponent
Oct 8, 2018
7f80ace
Merge branch 'master' of github.com:lumihq/purescript-react-basic
Oct 8, 2018
a51415a
Fix statelessComponent
Oct 9, 2018
3dfd110
Update types, distinguish initialState from state until make is called
Oct 10, 2018
9935a0c
Fix shouldUpdate ffi
Oct 10, 2018
c4a2988
Restore package-lock.json
Oct 12, 2018
7436a0c
Formatting
Oct 15, 2018
3560a7d
Provide self-bound event handling functions
Oct 17, 2018
21292cf
Clean up capture/monitor
Oct 17, 2018
d6ff680
A few more performance-related tweaks
Oct 17, 2018
d6cdfd9
Move displayName functions to FFI
Oct 17, 2018
7818536
More performance tweaks
Oct 17, 2018
5dba472
Still more perf :)
Oct 17, 2018
7e5b24b
Remove Compat module (moved to legacy example)
Oct 17, 2018
07df783
Capture forall bug
Oct 19, 2018
a03d67d
Refactor Self, plus more FFI perf improvements
Oct 23, 2018
8aa091e
Reorganize for docs
Oct 25, 2018
0fb25d1
Add docs
Oct 25, 2018
b5ebb89
Make it more difficult to run into the incompatible props bug
Oct 30, 2018
c17cdf3
Use Union instead of Record update
Oct 30, 2018
5fc857b
Fix displayNameFromSelf
Oct 30, 2018
546ed40
Export Compat module
Nov 2, 2018
9e7f205
Remove unused state in counter example
Nov 5, 2018
8e16027
Docs
Nov 6, 2018
d31f586
selectorRef
Nov 6, 2018
4430c12
Doc updates
Nov 7, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/.psc*
/.purs*
/.psa*
/.vscode/
56 changes: 11 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

[![Build Status](https://travis-ci.org/lumihq/purescript-react-basic.svg?branch=master)](https://travis-ci.org/lumihq/purescript-react-basic)

This package implements an opinionated set of bindings to the React library, optimizing for the most basic use cases.
This package implements an opinionated set of bindings over [React](https://reactjs.org), optimizing for correctness and simplifying basic use cases.

## Features

- All React DOM elements and attributes are supported (soon, events are a work in progress).
- An intuitive API for specifying props - no arrays of key value pairs, just records.
- An intuitive API for specifying props - simple records, no arrays of key value pairs.
- Attributes are optional, but type-checked. It is a type error to specify `href` as an integer, for example.
- An action/update pattern for local component state, inspired by [ReasonReact](https://reasonml.github.io/reason-react/).
- React lifecycles are available, but not in your way when you don't need them.
- Typeclasses, like `Eq props`, can be used in component definitions.

## Getting Started

Expand All @@ -18,49 +21,12 @@ You can install this package using Bower:
bower install git@github.com:lumihq/purescript-react-basic.git
```

Here is an example component which renders a label read from props along with a counter:
See [the documentation](https://pursuit.purescript.org/packages/purescript-react-basic/docs/React.Basic) for a detailed overview, or take a look at one of the examples:

```purescript
module Counter where
- [A Counter](./examples/counter/src/Counter.purs)
- [A controlled input](./examples/controlled-input/src/ControlledInput.purs)
- [Components](./examples/component/src/ToggleButton.purs) in [components](./examples/component/src/Container.purs)

import Prelude
## Migrating to v4 from v2 or v3

import React.Basic as React
import React.Basic.DOM as R
import React.Basic.Events as Events

-- The props for the component
type Props =
{ label :: String
}

-- 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 :: React.Component Props
component = React.component { displayName: "Counter", initialState, receiveProps, render }
where
initialState =
{ counter: 0
}

receiveProps _ =
pure unit

render { props, state, setState } =
R.button
{ onClick: Events.handler_ do
setState \s -> s { counter = s.counter + 1 }
, children: [ R.text (props.label <> ": " <> show state.counter) ]
}
```

This component can be used directly from JavaScript. For example, if you are using `purs-loader`:

```jsx
import {example as Example} from 'React.Basic.Example.purs';

const myComponent = () => (
<Example label='Increment' />
);
```
v4 includes a new (but deprecated) module, `React.Basic.Compat`. It matches most of the old API and types (except `setStateThen` and `isFirstMount`) to make upgrading easier and more gradual. You can find `^import\sReact\.Basic\b` and replace with `import React.Basic.Compat`, upgrade the package version, and proceed from there one component at a time (or only new components). See the documentation link above for more info on the v4 API.
24 changes: 10 additions & 14 deletions bower.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
{
"name": "purescript-react-basic",
"license": "Apache-2.0",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"output"
],
"ignore": ["**/.*", "node_modules", "bower_components", "output"],
"repository": {
"type": "git",
"url": "git://github.com/lumihq/purescript-react-basic.git"
},
"dependencies": {
"purescript-aff": "^5.0.2",
"purescript-console": "^4.1.0",
"purescript-effect": "^2.0.0",
"purescript-exceptions": "^4.0.0",
"purescript-functions": "^4.0.0",
"purescript-unsafe-coerce": "^4.0.0",
"purescript-nullable": "^4.0.0",
"purescript-typelevel-prelude": "^3.0.0",
"purescript-nullable": "^4.1.0",
"purescript-record": "^1.0.0",
"purescript-effect": "^2.0.0",
"purescript-web-events": "^1.0.0",
"purescript-typelevel-prelude": "^3.0.0",
"purescript-unsafe-coerce": "^4.0.0",
"purescript-web-dom": "^1.0.0",
"purescript-exceptions": "^4.0.0"
"purescript-web-events": "^1.0.0"
},
"devDependencies": {
"purescript-web-html": "^1.0.0",
"purescript-console": "^4.1.0"
"purescript-web-html": "^1.0.0"
}
}
25 changes: 15 additions & 10 deletions codegen/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const fs = require('fs');
const { props, voids, types, reserved } = require('./consts');
const fs = require("fs");
const { props, voids, types, reserved } = require("./consts");
const genFile = "../src/React/Basic/DOM/Generated.purs";

const header = `-- | ----------------------------------------
Expand All @@ -15,14 +15,15 @@ import React.Basic.Events (EventHandler)

`;

const printRecord = (elProps) => elProps.length ? `
( ${ elProps.map((p) =>
`${p} :: ${types[p] || "String"}`).join("\n , ")
}
)` : "()"
const printRecord = elProps =>
elProps.length
? `
( ${elProps.map(p => `${p} :: ${types[p] || "String"}`).join("\n , ")}
)`
: "()";

const domTypes = props.elements.html
.map((e) => {
.map(e => {
const noChildren = voids.includes(e);
const symbol = reserved.includes(e) ? `${e}'` : e;
return `
Expand All @@ -36,13 +37,17 @@ const domTypes = props.elements.html
=> Record attrs
-> JSX
${symbol} = element (unsafeCreateDOMComponent "${e}")${
noChildren ? "" : `
noChildren
? ""
: `

${e}_ :: Array JSX -> JSX
${e}_ children = ${symbol} { children }`
}
`;
}).map((x) => x.replace(/^\n\ {4}/, "").replace(/\n\ {4}/g, "\n")).join("\n");
})
.map(x => x.replace(/^\n\ {4}/, "").replace(/\n\ {4}/g, "\n"))
.join("\n");

console.log(`Writing "${genFile}" ...`);
fs.writeFileSync(genFile, header + domTypes);
Expand Down
27 changes: 15 additions & 12 deletions examples/component/src/Container.purs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
module Container where

import React.Basic as React
import Prelude

import React.Basic (Component, JSX, createComponent, makeStateless)
import React.Basic.DOM as R
import ToggleButton as ToggleButton
import ToggleButton (toggleButton)

component :: Component Unit
component = createComponent "Container"

component :: React.Component {}
component = React.stateless { displayName: "Container", render }
where
render _ =
R.div
{ children:
[ React.element ToggleButton.component { label: "A" }
, React.element ToggleButton.component { label: "B" }
]
}
toggleButtonContainer :: JSX
toggleButtonContainer = unit # makeStateless component \_ ->
R.div
{ children:
[ toggleButton { label: "A" }
, toggleButton { label: "B" }
]
}
5 changes: 2 additions & 3 deletions examples/component/src/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ module Main where

import Prelude

import Container as Container
import Container (toggleButtonContainer)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Exception (throw)
import React.Basic (element)
import React.Basic.DOM (render)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
Expand All @@ -19,5 +18,5 @@ main = do
case container of
Nothing -> throw "Container element not found."
Just c ->
let app = element Container.component {}
let app = toggleButtonContainer
in render app c
39 changes: 23 additions & 16 deletions examples/component/src/ToggleButton.purs
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,40 @@ module ToggleButton where
import Prelude

import Effect.Console (log)
import React.Basic as React
import React.Basic (Component, JSX, StateUpdate(..), capture_, createComponent, make)
import React.Basic.DOM as R
import React.Basic.Events as Events

component :: Component Props
component = createComponent "ToggleButton"

type Props =
{ label :: String
}

component :: React.Component Props
component = React.component { displayName: "ToggleButton", initialState, receiveProps, render }
where
initialState =
data Action
= Toggle

toggleButton :: Props -> JSX
toggleButton = make component
{ initialState:
{ on: false
}

receiveProps _ =
pure unit
, update: \self -> case _ of
Toggle ->
UpdateAndSideEffects
self.state { on = not self.state.on }
\nextSelf -> do
log $ "next state: " <> show nextSelf.state

render { props, state, setStateThen } =
, render: \self ->
R.button
{ onClick: Events.handler_ do
setStateThen (\s -> s { on = not s.on }) \nextState -> do
log $ "nextState: " <> show nextState
{ onClick: capture_ self Toggle
, children:
[ R.text props.label
, R.text if state.on
then " On"
else " Off"
[ R.text self.props.label
, R.text if self.state.on
then " On"
else " Off"
]
}
}
47 changes: 28 additions & 19 deletions examples/controlled-input/src/ControlledInput.purs
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,43 @@ module ControlledInput where
import Prelude

import Data.Maybe (Maybe(..), fromMaybe, maybe)
import React.Basic (Component, JSX, StateUpdate(..), capture, createComponent, make)
import React.Basic as React
import React.Basic.DOM as R
import React.Basic.DOM.Events (preventDefault, targetValue, timeStamp)
import React.Basic.Events as Events
import React.Basic.DOM.Events (targetValue, timeStamp)
import React.Basic.Events (merge)

component :: React.Component {}
component = React.component { displayName: "ControlledInput", initialState, receiveProps, render }
where
initialState =
component :: Component Props
component = createComponent "ControlledInput"

type Props = Unit

data Action
= ValueChanged String Number

controlledInput :: Props -> JSX
controlledInput = make component
{ initialState:
{ value: "hello world"
, timeStamp: Nothing
, timestamp: Nothing
}

receiveProps _ =
pure unit
, update: \self -> case _ of
ValueChanged value timestamp ->
Update self.state
{ value = value
, timestamp = Just timestamp
}

render { state, setState } =
, render: \self ->
React.fragment
[ R.input
{ onChange:
Events.handler
(preventDefault >>> Events.merge { targetValue, timeStamp })
\{ timeStamp, targetValue } ->
setState _ { value = fromMaybe "" targetValue
, timeStamp = Just timeStamp
}
, value: state.value
capture self (merge { targetValue, timeStamp })
\{ timeStamp, targetValue } -> ValueChanged (fromMaybe "" targetValue) timeStamp
, value: self.state.value
}
, R.p_ [ R.text ("Current value = " <> show state.value) ]
, R.p_ [ R.text ("Changed at = " <> maybe "never" show state.timeStamp) ]
, R.p_ [ R.text ("Current value = " <> show self.state.value) ]
, R.p_ [ R.text ("Changed at = " <> maybe "never" show self.state.timestamp) ]
]
}
5 changes: 2 additions & 3 deletions examples/controlled-input/src/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ module Main where

import Prelude

import ControlledInput as ControlledInput
import ControlledInput (controlledInput)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Exception (throw)
import React.Basic (element)
import React.Basic.DOM (render)
import Web.DOM.NonElementParentNode (getElementById)
import Web.HTML (window)
Expand All @@ -19,5 +18,5 @@ main = do
case container of
Nothing -> throw "Container element not found."
Just c ->
let app = element ControlledInput.component {}
let app = controlledInput unit
in render app c
6 changes: 3 additions & 3 deletions examples/counter/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"dependencies": {
"react": "^16.4.2",
"react-dom": "^16.4.2"
"react": "16.6.0",
"react-dom": "16.6.0"
},
"devDependencies": {
"browserify": "^16.2.2"
"browserify": "16.2.3"
}
}
Loading