Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/paf31/purescript-thermite
A simple PureScript wrapper for React
https://github.com/paf31/purescript-thermite
Last synced: 16 days ago
JSON representation
A simple PureScript wrapper for React
- Host: GitHub
- URL: https://github.com/paf31/purescript-thermite
- Owner: paf31
- License: mit
- Created: 2014-12-20T00:39:33.000Z (about 10 years ago)
- Default Branch: master
- Last Pushed: 2022-12-02T23:17:48.000Z (about 2 years ago)
- Last Synced: 2024-12-07T04:12:21.286Z (about 1 month ago)
- Language: PureScript
- Size: 521 KB
- Stars: 350
- Watchers: 13
- Forks: 55
- Open Issues: 14
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-purescript - purescript-thermite
- awesome-purescript - purescript-thermite - A simple wrapper for ReactJS inspired by `react-blaze`. (UI Libraries)
README
## purescript-thermite
[![Pursuit](http://pursuit.purescript.org/packages/purescript-thermite/badge)](http://pursuit.purescript.org/packages/purescript-thermite/)
`purescript-thermite` is a PureScript wrapper for [`purescript-react`](http://github.com/purescript-contrib/purescript-react). It does not provide all of the functionality of React, but instead provides a clean API to the most commonly-used parts of its API. It is possible to use `purescript-react` for more specialized use cases.
- [Try Thermite!](http://try.purescript.org/?backend=thermite)
- [Module Documentation](generated-docs/Thermite.md)
- [Task List Example](http://try.purescript.org/?gist=f5f273e4c5e4161fceff&backend=thermite)## Building
```
bower update
pulp build
pulp test -r cat > html/index.js
```You can also now use `npm test` to run the test command above.
## Getting Started
Thermite components are defined in parts:
- A type of _actions_, which represents the actions a user can take on our component
- A type of _states_, which represents the internal state of our component
- An initial state
- A rendering function, which takes the current component state and properties, and creates a HTML document
- A function which interprets _actions_, by modifying the state and/or running some (possibly asynchronous) computationsHere is an example. We'll build a component which displays the value of a integer-valued counter.
First of all, we need to import some modules:
```purescript
import Thermite as Timport React as R
import React.DOM as R
import React.DOM.Props as RP
import ReactDOM as RDOM
```In our component, users will be able to take two actions - increment and decrement - which will be represented as buttons later:
```purescript
data Action = Increment | Decrement
```The state of our component is just an integer:
```purescript
type State = { counter :: Int }
```The initial state is zero:
```purescript
initialState :: State
initialState = { counter: 0 }
```Our rendering function uses the `React.DOM.*` modules to create a HTML document containing a label and two buttons. The buttons' `onclick` handlers are given functions which generate the correct actions. The `dispatch` function, which is passed as the first argument to `render`, can be used to build such a function, by providing an action:
```purescript
render :: T.Render State _ Action
render dispatch _ state _ =
[ R.p' [ R.text "Value: "
, R.text $ show state.counter
]
, R.p' [ R.button [ RP.onClick \_ -> dispatch Increment ]
[ R.text "Increment" ]
, R.button [ RP.onClick \_ -> dispatch Decrement ]
[ R.text "Decrement" ]
]
]
```The `performAction` function interprets actions by passing a function to the state update function, which is responsible for updating the state using record updates:
```purescript
performAction :: T.PerformAction State _ Action
performAction Increment _ _ = void (T.cotransform (\state -> state { counter = state.counter + 1 }))
performAction Decrement _ _ = void (T.cotransform (\state -> state { counter = state.counter - 1 }))
```_Note_: `PerformAction` returns a _coroutine_, which can emit many asynchronous state updates using `cotransform`. This approach also allows us to create asynchronous and/or chunked action handlers (using AJAX or websockets, for example):
```purescript
getIncrementValueFromServer :: Aff IntperformAction :: T.PerformAction State _ Action
performAction Increment _ _ = do
Just amount <- lift getIncrementValueFromServer
void $ T.cotransform $ \state -> state { counter = state.counter + amount }
```With these pieces, we can create a `Spec` for our component:
```purescript
spec :: T.Spec State (T.WithChildren ()) Action
spec = T.Spec {performAction, render}
```> Note that the new purescript-react needs some typechecking assistance
> for `props` - `Spec` needs an extra `{ children :: Children | props }` field in its props,
> yet that field is not necessary when creating a react element with its `props` argument.
>
> `WithChildren props` is just an alias for `{ children :: Children | props }`.Finally, in `main`, the `defaultMain` function from the
[purescript-thermite-dom](https://github.com/athanclark/purescript-thermite-dom)
library can be used to render our component to the document body by specifying the initial state:```purescript
import Thermite.DOM (defaultMain)main = defaultMain spec (const initialState) "MyComponent" {}
```## Combining Components
The `Spec` type is an instance of the `Semigroup` and `Monoid` type classes. These instances can be used to combine different components with the same `state` and `action` types.
In practice, the `state` and `action` types will not always match for the different subcomponents, so Thermite provides combinators for changing these type arguments: `focus` and `foreach`. These combinators are heavily inspired by the [OpticUI](https://github.com/zrho/purescript-optic-ui) library.
See the [example project](test/) for examples of these kinds of composition.
### `focus`
`focus` (and the related functions `focusState` and `match`) are used to enlarge the state and action types, to make it possible to embed a component inside a larger component.
`focus` takes a _lens_, which identifies the `state` type as a part of the state type of the larger component, and a _prism_, which identifies all actions of the smaller component as actions for the larger component. `focusState` is used when only the `state` type needs to be changed, and `match` is used when only the `action` type needs to be changed.
As a simple example, we can combine two subcomponents by using a `Tuple` to store both states, and `Either` to combine both sets of actions:
```purescript
spec1 :: Spec S1 _ A1
spec2 :: Spec S2 _ A2spec :: Spec (Tuple S1 S2) _ (Either A1 A2)
spec = focus _1 _Left spec1 <> focus _2 _Right spec2
```Here, `_1` and `_Left` embed `spec1` inside `spec`, using the left components of both the state `Tuple` and the `Either` type of actions. `_2` and `_Right` similarly embed `spec2`, using the right components.
_focus_ is responsible for directing the various actions to the correct components, and updating the correct parts of the state.
### `split`
`split` is used to handle child components which might not be present, for
example, when a parent object contains a `Maybe` state.```purescript
type Parent = { child :: Maybe child }_child :: LensP Parent (Maybe Child)
_child = lens _.child (_ { child = _ })_ChildAction :: PrismP ParentAction ChildAction
childSpec :: Spec Child _ ChildAction
spec :: Spec Parent _ ParentAction
spec = focus _child _ChildAction $ split _Just childSpec
```### `foreach`
Where `focus` embeds a single subcomponent inside another component, `foreach` embeds a whole collection of subcomponents.
`foreach` turns a `Spec eff state props action` into a `Spec eff (List state) props (Tuple Int action)`. Note that the state type has been wrapped using `List`, since the component now tracks state for each element of the collection. Also, the `action` type has been replaced with `Tuple Int action`. This means that when an action occurs, it is accompanied by the index of the element in the collection which it originated from.