Skip to content

Commit

Permalink
Update doc for scalajs-react 1.2.0
Browse files Browse the repository at this point in the history
Closes #449
  • Loading branch information
japgolly committed Mar 12, 2018
1 parent f2c4520 commit 611c3ed
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 168 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Includes a router, testing utils, performance utils, more.
* [test-state](https://github.com/japgolly/test-state/) - Integration/Functional/Property testing for scalajs-react.
* [scalajs-benchmark](https://github.com/japgolly/scalajs-benchmark/)
* [chandu0101 / scalajs-react-components](https://github.com/chandu0101/scalajs-react-components)
* [payalabs / scalajs-react-bridge](https://github.com/payalabs/scalajs-react-bridge) - Boilterplate free use of JS components
* [payalabs / scalajs-react-bridge](https://github.com/payalabs/scalajs-react-bridge) - Boilterplate free use of JS components
* [payalabs / scalajs-react-mdl](https://github.com/payalabs/scalajs-react-mdl) - (Material Design Lite components)
* [cquiroz / scalajs-react-virtualized](https://github.com/cquiroz/scalajs-react-virtualized) - Facade for react-virtualized
* [cquiroz / scalajs-react-clipboard](https://github.com/cquiroz/scalajs-react-clipboard) - Facade for react-copy-to-clipboard
Expand All @@ -60,9 +60,9 @@ Includes a router, testing utils, performance utils, more.
* [scala-weather-app](https://github.com/malaman/scala-weather-app) - Yet another weather application, based on Scala.js, scalajs-react and Playframework

##### Requirements:
* React 15.3+
* Scala 2.11 or 2.12.
* Scala.JS 0.6.17+
* React ≥ 16
* Scala ≥ 2.11
* Scala.JS 0.6.22

##### Support:
If you like what I do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@ import scala.scalajs.js

object ReactFragment {

/** Elements keys are optional. */
/** Unlike [[VdomArray]],
*
* - This is immutable.
* - Elements may, but needn't have keys.
* - The result can be assigned a key.
*/
def apply(ns: VdomNode*): VdomElement =
create(null, ns: _*)

/** Elements keys are optional. */
/** Unlike [[VdomArray]],
*
* - This is immutable.
* - Elements may, but needn't have keys.
* - The result can be assigned a key.
*/
def withKey(key: Key)(ns: VdomNode*): VdomElement = {
val jsKey: Raw.React.Key = key
val props = js.Dynamic.literal("key" -> jsKey.asInstanceOf[js.Any])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ object VdomElement {

// =====================================================================================================================

/** Elements must have keys. Array itself cannot. */
/** This is mutable so don't let it escape a local pure function.
* Elements must have keys.
* VdomArray itself cannot be assigned a key.
*/
final class VdomArray(val rawArray: js.Array[Raw.React.Node]) extends VdomNode(rawArray.asInstanceOf[Raw.React.Node]) {

def +=(n: VdomNode): this.type = {
Expand Down
25 changes: 19 additions & 6 deletions doc/EXTRA.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ libraryDependencies += "com.github.japgolly.scalajs-react" %%% "extra" % "1.2.0"

#### Contents

- [`Ajax`](#ajax)
- [`StateSnapshot`](#statesnapshot)
- Component Mixins:
- [Broadcaster and Listenable](#broadcaster-and-listenable)
Expand All @@ -18,9 +19,21 @@ libraryDependencies += "com.github.japgolly.scalajs-react" %%% "extra" % "1.2.0"
- [OnUnmount](#onunmount)
- [TimerSupport](#timersupport)

<br>

`Ajax`
======

`japgolly.scalajs.react.extra.Ajax` is a helper for AJAX that is purely-functional
in that it runs a `Callback`, and accepts XHR-callbacks as `Callback` instances.

A live demo with accompanying code is available here:<br>
[https://japgolly.github.io/scalajs-react/#examples/ajax](https://japgolly.github.io/scalajs-react/#examples/ajax)


`StateSnapshot`
===============

Consider:
1. React has unidirectional flow (yay) and pure render methods (yay!).
2. Stateful components are like mutable variables, stateless components are like immutable values.
Expand All @@ -29,11 +42,13 @@ Consider:
How does one write components that appear stateful yet maintain referential-transparency?
By declaring the following in the component's props:
1. `S` - The current state value to use in the view.
2. `S => Callback` - A function that accepts a new state and returns a `Callack`.
2. `(Option[S], Callback) => Callback` - A function that accepts `setState` arguments
(namely: an optional new state, and a callback for React to execute once the new state has been applied),
and returns a `Callback`.
The component calls this to request a new state be recorded and then just assumes that the function does something
meaningful. This is great because no assumptions about the larger context are encoded; it has just enough to do its job.

`StateSnapshot[S]` encapsulates this `(S, S => Callback)` pattern.
`StateSnapshot[S]` encapsulates this `(S, (Option[S], Callback) => Callback)` pattern.
It's called StateSnapshot because it takes a snapshot of state at its current value.
It also supports optional [`Reusability`](PERFORMANCE.md).

Expand All @@ -47,8 +62,7 @@ Construction:
* `StateSnapshot.zoom(…)(s).setStateVia($)`
* `StateSnapshot.zoom(…).of($)`

A live demo with accompanying code is available here:

A live demo with accompanying code is available here:<br>
https://japgolly.github.io/scalajs-react/#examples/state-snapshot


Expand Down Expand Up @@ -84,8 +98,7 @@ EventListener
* Uninstalls event listeners when component is unmounted.
* By default, listens to the component node's events. Can specify other event targets (eg. `window`, `document`)

A live demo with accompanying code is available here:

A live demo with accompanying code is available here:<br>
https://japgolly.github.io/scalajs-react/#examples/event-listener


Expand Down
8 changes: 4 additions & 4 deletions doc/FP.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ Monocle
```scala
libraryDependencies ++= Seq(
"com.github.japgolly.scalajs-react" %%% "ext-monocle" % "1.2.0",
"com.github.julien-truffaut" %%% "monocle-core" % "1.4.0",
"com.github.julien-truffaut" %%% "monocle-macro" % "1.4.0"
"com.github.julien-truffaut" %%% "monocle-core" % "1.5.0",
"com.github.julien-truffaut" %%% "monocle-macro" % "1.5.0"
)

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
```

A module with a extensions for [Monocle](https://github.com/julien-truffaut/Monocle) also exists under `ext-monocle`.
Expand All @@ -65,7 +65,7 @@ libraryDependencies ++= Seq(
"com.github.julien-truffaut" %%% "monocle-macro" % "1.5.0-cats"
)

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)

Same as the `ext-monocle` module but using the cats variant of monocle

Expand Down
6 changes: 3 additions & 3 deletions doc/IDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ object $NAME$ {
}

//implicit val reusabilityProps: Reusability[Props] =
// Reusability.caseClass
// Reusability.derive

final class Backend($: BackendScope[Props, Unit]) {
def render(p: Props): VdomElement =
Expand Down Expand Up @@ -65,7 +65,7 @@ object $NAME$ {
}

//implicit val reusabilityProps: Reusability[Props] =
// Reusability.caseClass
// Reusability.derive

final case class State()

Expand All @@ -74,7 +74,7 @@ object $NAME$ {
State()

//implicit val reusability: Reusability[State] =
// Reusability.caseClass
// Reusability.derive
}

final class Backend($: BackendScope[Props, Unit]) {
Expand Down
21 changes: 8 additions & 13 deletions doc/PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The strategy is simple: avoid work where possible.
These utilities help you avoid work in two ways.

1. By making components' `shouldComponentUpdate` fns both easy to create, and accurate (safe). If it compiles, the logic in `shouldComponentUpdate` will be what you expect.
2. By allowing you to cache your own arbitrary data, and build on it in a way such that derivative data is also cached effeciently.
2. By allowing you to cache your own arbitrary data, and build on it in a way such that derivative data is also cached efficiently.

```scala
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "extra" % "1.2.0"
Expand All @@ -33,7 +33,6 @@ libraryDependencies += "com.github.japgolly.scalajs-react" %%% "extra" % "1.2.0"
- [Initial instances](#initial-instances)
- [Derivative instances](#derivative-instances)
- [`Px` doesn't have `Reusability`](#px-doesnt-have-reusability)
- [`ReactAddons.Perf`](#reactaddonsperf)


`Reusability`
Expand All @@ -56,16 +55,16 @@ in many cases it is sufficient to check only the ID field, the update-date, or t
When building your component, pass in `Reusability.shouldComponentUpdate` to `.configure` on the component builder.

It will not compile until it knows how to compare the reusability of your props and state.
Out-of-the-box, it knows how to compare Scala primatives, `String`s, `Option`, `Either`, Scala tuples, `js.UndefOr`,
Out-of-the-box, it knows how to compare Scala primitives, `String`s, `Option`, `Either`, Scala tuples, `js.UndefOr`,
Scala and JS `Date`s, `UUID`s, `Set`s, `List`s, `Vector`s,
and Scalaz classes `\/` and `\&/`. For all other types, you'll need to teach it how. Use one of the following methods:

* `Reusability((A, B) => Boolean)` to hand-write custom logic.
* `Reusability.by_==` uses universal equality (ie. `a1 == a2`).
* `Reusability.byRef` uses reference equality (ie. `a1 eq a2`).
* `Reusability.byRefOr_==` uses reference equality and if different, tries universal equality.
* `Reusability.caseClass` for case classes of your own.
* `Reusability.caseClassDebug` as above, but shows you the code that the macro generates.
* `Reusability.derive` for ADTs of your own.
* `Reusability.deriveDebug` as above, but shows you the code that the macro generates.
* `Reusability.caseClassExcept` for case classes of your own where you want to exclude some fields.
* `Reusability.caseClassExceptDebug` as above, but shows you the code that the macro generates.
* `Reusability.by(A => B)` to use a subset (`B`) of the subject data (`A`).
Expand All @@ -90,7 +89,7 @@ case class Picture(id: Long, url: String, title: String)
case class Props(name: String, age: Option[Int], pic: Picture)

implicit val picReuse = Reusability.by((_: Picture).id) // ← only check id
implicit val propsReuse = Reusability.caseClass[Props] // ← check all fields
implicit val propsReuse = Reusability.derive[Props] // ← check all fields

val component = ScalaComponent.builder[Props]("Demo")
.render_P(p =>
Expand Down Expand Up @@ -160,6 +159,8 @@ To create a `Reusable` value, use one of the following methods:
* `Reusable.by_==` uses universal equality (ie. `a1 == a2`).
* `Reusable.byRef` uses reference equality (ie. `a1 eq a2`).
* `Reusable.byRefOr_==` uses reference equality and if different, tries universal equality.
* `Reusable.callback{,Option}ByRef` uses reference equality over `Callback` and `CallbackOption`
* `Reusable.byRefIso` - Compare by reference through an isomorphism
* `Reusable.{always,never,const(bool)}` for a hard-coded reusability decision.

#### Mapping without affecting reusability
Expand Down Expand Up @@ -242,7 +243,7 @@ class Backend(bs: BackendScope[_, State]) {

case class PersonEditorProps(name: String, update: String ~=> Callback) // ← Notice the ~=>

implicit val propsReuse = Reusability.caseClass[PersonEditorProps]
implicit val propsReuse = Reusability.derive[PersonEditorProps]

val personEditor = ScalaComponent.builder[PersonEditorProps]("PersonEditor")
.render_P(p =>
Expand Down Expand Up @@ -520,9 +521,3 @@ class Component1Backend {
Component2(Component2Props(px)) // .value() called implicitly
}
```

`ReactAddons.Perf`
==================
React addons come with performance tools. See https://facebook.github.io/react/docs/perf.html.

Access via `ReactAddons.Perf`.
36 changes: 19 additions & 17 deletions doc/REFS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@ This document describes how to do the same within scalajs-react.

## Refs to VDOM tags

1. Create a variable in which to store the DOM reference.
Its type should be whatever the DOM's type is (wrap it in `Option` for additional safety if desired.)
The recommended place to store this is inside a component Backend, as a `private var`.
2. On your [`VdomTag`](TYPES.md), call `.ref(vdomTag => Unit)` on it and provide a function that updates your variable.
3. To access the DOM from callbacks, just access the variable.
1. Create a `Ref` in which to store the DOM reference.
Its type should be whatever the DOM's type.
Store this inside a component Backend, as a `private val`.
2. On your [`VdomTag`](TYPES.md), call `.withRef(ref)` and pass it your reference.
3. To access the DOM from callbacks, call `.get` or `.foreach`.

Example (excerpts from [CallbackOption online demo](https://japgolly.github.io/scalajs-react/#examples/callback-option)):
```scala
object CallbackOptionExample {

class Backend($: BackendScope[Unit, State]) {

// Prepare a variable
private var outerRef: html.Element = _
// Create the reference
private val outerRef = Ref[html.Element]

// Wire it up from VDOM
def render(s: State) =
OuterDiv.ref(outerRef = _)(...)
OuterDiv.withRef(outerRef)(...)

// Use it
def init: Callback =
Callback(outerRef.focus())
outerRef.foreach(_.focus())
}

val Main = ScalaComponent.builder[Unit]("CallbackOption example")
Expand All @@ -48,28 +48,30 @@ Here is another example: [Refs online demo](https://japgolly.github.io/scalajs-r

## Refs to Scala components

1. Call `ScalaComponent.mutableRefTo(comp)` to create a reference variable.
This is mutable and should *not* be shared.
The recommended place to store this is inside a component Backend, as a `private val`.
1. Call `Ref.toScalaComponent(comp)` to create a reference. Store this inside a component Backend, as a `private val`.
2. Instead of using the component directly to instantiate it, call `.component` on the ref you created.
3. To access the DOM from callbacks, call `.value` on the ref.

Example:
```scala
val Double = ScalaComponent.builder[Int]("Doubler")
val DoubleComp = ScalaComponent.builder[Int]("Doubler")
.render_P(i => <.p(s"$i + $i = ${i << 1}"))
.build

class Backend {

// Create a mutable reference
private val ref = ScalaComponent.mutableRefTo(Double)
private val ref = Ref.toScalaComponent(DoubleComp)

// Wire it up from VDOM
def render = <.div(ref.component(123))

// Use it
def onMount = Callback.log("DOM HTML: ", ref.value.getDOMNode.outerHTML)
// Use it from a lifecycle callback
def onMount: Callback =
ref
.get
.map(_.getDOMNode.asElement.outerHTML)
.flatMapCB(Callback.log("DOM HTML: ", _))
}

val Exapmle = ScalaComponent.builder[Unit]("Example")
Expand All @@ -81,7 +83,7 @@ val Exapmle = ScalaComponent.builder[Unit]("Example")
## Refs to JS components

Same as with Scala components;
just change `ScalaComponent.mutableRefTo` to `JsComponent.mutableRefTo`.
just change `Ref.toScalaComponent` to `Ref.toJsComponent`.

## Refs to functional components

Expand Down
37 changes: 7 additions & 30 deletions doc/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,40 +26,17 @@ Setup

```scala
// scalajs-react test module
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "test" % "1.2.0" % "test"
libraryDependencies += "com.github.japgolly.scalajs-react" %%% "test" % "1.2.0" % Test

// React JS itself.
// NOTE: Requires react-with-addons.js instead of just react.js
jsDependencies ++= Seq(

"org.webjars.bower" % "react" % "15.6.1" % "test"
/ "react-with-addons.js"
minified "react-with-addons.min.js"
commonJSName "React",

"org.webjars.bower" % "react" % "15.6.1" % "test"
/ "react-dom.js"
minified "react-dom.min.js"
dependsOn "react-with-addons.js"
commonJSName "ReactDOM",

"org.webjars.bower" % "react" % "15.6.1" % "test"
/ "react-dom-server.js"
minified "react-dom-server.min.js"
dependsOn "react-dom.js"
commonJSName "ReactDOMServer"),

// Indicate that unit tests will access the DOM
requiresDOM := true

// Compile tests to JS using fast-optimisation
scalaJSStage in Test := FastOptStage
```

3. To [workaround](https://github.com/scala-js/scala-js/issues/1555) a [PhantomJS bug](https://github.com/ariya/phantomjs/issues/13112) that causes tests to crash if they write to stderr, copy [`PhantomJS2Env.scala`](https://github.com/japgolly/scalajs-react/blob/v1.0.0-RC1/project/PhantomJS2Env.scala) to your `project` directory and add this to SBT:
jsDependencies +=

```scala
jsEnv in Test := new PhantomJS2Env(scalaJSPhantomJSClassLoader.value)
"org.webjars.npm" % "react-dom" % "16.2.0" % Test
/ "umd/react-dom-test-utils.development.js"
minified "umd/react-dom-test-utils.production.min.js"
dependsOn "umd/react-dom.development.js"
commonJSName "ReactTestUtils"
```


Expand Down
Loading

0 comments on commit 611c3ed

Please sign in to comment.