Skip to content

Commit f988eb5

Browse files
committed
Update extra/test doco for v0.10
1 parent e5b46a1 commit f988eb5

File tree

7 files changed

+94
-107
lines changed

7 files changed

+94
-107
lines changed

doc/EXTRA.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,34 +105,36 @@ Accrues procedures to be run automatically when its component unmounts.
105105
##### Example
106106
```scala
107107
class MyBackend extends OnUnmount {
108-
def init(): Unit = {
109-
console.log("Initialising now...")
110-
onUnmount { console.log("Component unmounting...") }
111-
}
108+
def init: Callback =
109+
Callback.log("Initialising now...") >>
110+
onUnmount( Callback.log("Component unmounting...") )
112111
}
113112

114113
val eg = ReactComponentB[Unit]("Example")
115114
.stateless
116115
.backend(_ => new MyBackend)
117116
.render(_ => ???)
118-
.componentWillMount(_.backend.init())
117+
.componentWillMount(_.backend.init)
119118
.configure(OnUnmount.install)
120119
.buildU
121120
```
122121

123-
SetInterval
124-
===========
125-
Alternative to `window.setInterval` that automatically unregisters installed callbacks when its component unmounts.
122+
TimerSupport
123+
============
124+
Alternatives to `window.setTimeout`/`window.setInterval` that automatically unregister installed callbacks
125+
when the component unmounts
126126

127127
##### Example
128128
```scala
129-
class MyBackend extends SetInterval
129+
import scala.concurrent.duration._
130+
131+
class MyBackend extends TimerSupport
130132

131133
val Timer = ReactComponentB[Unit]("Timer")
132134
.initialState(0L)
133135
.backend(_ => new MyBackend)
134-
.render((_,s,_) => div("Seconds elapsed: ", s))
136+
.render_S(s => <.div("Seconds elapsed: ", s))
135137
.componentDidMount(c => c.backend.setInterval(c.modState(_ + 1), 1.second))
136-
.configure(SetInterval.install)
138+
.configure(TimerSupport.install)
137139
.buildU
138140
```

doc/PERFORMANCE.md

Lines changed: 61 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,26 @@ When building your component, pass in `Reusability.shouldComponentUpdate` to you
5555

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

60-
* `Reusability.byRef` uses reference equality (ie. `a eq b`).
6161
* `Reusability.by_==` uses universal equality (ie. `a == b`).
62-
* `Reusability.byRef_==` uses reference equality and if different, tries universal equality.
62+
* `Reusability.byRef` uses reference equality (ie. `a eq b`).
63+
* `Reusability.byRefOr_==` uses reference equality and if different, tries universal equality.
6364
* `Reusability.caseClass` for case classes of your own.
6465
* `Reusability.caseClassDebug` as above, but shows you the code that the macro generates.
6566
* `Reusability.by(A => B)` to use a subset (`B`) of the subject data (`A`).
6667
* `Reusability.fn((A, B) => Boolean)` to hand-write custom logic.
67-
* `Reusability.byEqual` uses a Scalaz `Equal` typeclass.
68-
* `Reusability.byRefOrEqual` uses reference equality and if different, tries using a Scalaz `Equal` typeclass.
6968
* `Reusability.byIterator` uses an `Iterable`'s iterator to check each element in order.
7069
* `Reusability.indexedSeq` uses `.length` and `.apply(index)` to check each element in order.
70+
* `Reusability.{double,float}` exist and require a tolerance to be specified.
7171
* `Reusability.{always,never,const(bool)}` are available too.
7272

73+
If you're using the Scalaz module, you also gain:
74+
* `Reusability.byEqual` uses a Scalaz `Equal` typeclass.
75+
* `Reusability.byRefOrEqual` uses reference equality and if different, tries using a Scalaz `Equal` typeclass.
76+
77+
7378
#### Example
7479
The following component will only re-render when one of the following change:
7580
* `props.name`
@@ -84,8 +89,7 @@ The following component will only re-render when one of the following change:
8489
implicit val propsReuse = Reusability.caseClass[Props] // ← check all fields
8590

8691
val component = ReactComponentB[Props]("Demo")
87-
.stateless
88-
.render((p, _) =>
92+
.render_P(p =>
8993
<.div(
9094
<.p("Name: ", p.name),
9195
<.p("Age: ", p.age.fold("Unknown")(_.toString)),
@@ -136,46 +140,45 @@ provide no means of determining whether a component can safely skip its update.
136140
In this example `personEditor` will only rerender if `props.name` changes, or the curried `PersonId` in its `props.update` function changes (which it won't - observable from the code).
137141

138142
```scala
143+
type State = Map[PersonId, PersonData]
139144
type PersonId = Long
140145
type PersonData = String
141146

142-
val topComponent = ReactComponentB[Map[PersonId, PersonData]]("Demo")
143-
.getInitialState(identity)
144-
.backend(new Backend(_))
145-
.render(_.backend.render)
147+
val topComponent = ReactComponentB[State]("Demo")
148+
.initialState_P(identity)
149+
.renderBackend[Backend]
146150
.build
147151

148-
class Backend($: BackendScope[_, Map[PersonId, PersonData]]) {
152+
class Backend($: BackendScope[_, State]) {
149153

150154
val updateUser = ReusableFn((id: PersonId, data: PersonData) => // ← Create a 2-arg fn
151-
$.modStateIO(map => map.updated(id, data)))
155+
$.modState(map => map.updated(id, data)))
152156

153-
def render =
157+
def render(state: State) =
154158
<.div(
155-
$.state.map { case (id, name) =>
159+
state.map { case (id, name) =>
156160
personEditor.withKey(id)(PersonEditorProps(name, updateUser(id))) // ← Apply 1 arg
157161
}.toJsArray
158162
)
159163
}
160164

161-
case class PersonEditorProps(name: String, update: String ~=> IO[Unit]) // ← Notice the ~=>
165+
case class PersonEditorProps(name: String, update: String ~=> Callback) // ← Notice the ~=>
162166

163167
implicit val propsReuse = Reusability.caseClass[PersonEditorProps]
164168

165169
val personEditor = ReactComponentB[PersonEditorProps]("PersonEditor")
166-
.stateless
167-
.render((p, _) =>
170+
.render_P(p =>
168171
<.input(
169172
^.`type` := "text",
170173
^.value := p.name,
171-
^.onChange ~~> ((e: ReactEventI) => p.update(e.target.value)))) // ← Use as normal
174+
^.onChange ==> ((e: ReactEventI) => p.update(e.target.value)))) // ← Use as normal
172175
.configure(Reusability.shouldComponentUpdate) // ← shouldComponentUpdate like magic
173176
.build
174177
```
175178

176179
#### WARNING!
177180

178-
**DO NOT** feed the ReusableFn(...) constructor a function directly *derived* from a component's props or state.
181+
**DO NOT** feed the `ReusableFn(...)` constructor a function directly *derived* from a component's props or state.
179182
Access to props/state on the right-hand side of the function args is ok but if the function itself is a result of the
180183
props/state, the function will forever be based on data that can go stale.
181184

@@ -185,45 +188,45 @@ Example:
185188
case class Props(person: ReusableVar[Person], other: Other)
186189

187190
// THIS IS BAD!!
188-
ReusableFn($.props.person setL Props.name)
191+
ReusableFn($.props.runNow().person setL Props.name)
189192

190193
// It is equivalent to:
191-
val g: String => IO[Unit] = $.props.person setL Person.name // ← $.props is evaluated once here
192-
val f: String ~=> IO[Unit] = ReusableFn(g) // ← …and never again.
194+
val g: String => Callback = $.props.runNow().person setL Person.name // ← $.props is evaluated once here
195+
val f: String ~=> Callback = ReusableFn(g) // ← …and never again.
193196
```
194197

195198
Alternatives:
196199

197200
1. Use `ReusableFn.byName`:
198201

199202
```scala
200-
ReusableFn.byName($.props.person setL Person.name)
203+
ReusableFn.byName($.props.runNow().person setL Person.name)
201204
```
202205

203206
2. Create a function with `$` on the right-hand side:
204207

205208
```scala
206-
ReusableFn(str => $.props.person.setL(Person.name)(str))
209+
ReusableFn(str => $.props.flatMap(_.person.setL(Person.name)(str)))
207210
```
208211

209212

210213
#### Tricks
211214

212215
To cater for some common use cases, there are few convenience methods that are useful to know.
213-
For these examples imagine `$` to be your component's scope instance, eg. `BackendScope[_,S]`, `ComponentScopeM[_,S,_,_]` or similar.
216+
For these examples imagine `$` to be your component's scope instance, eg. `BackendScope[_,S]`, `CompScope.Mounted[_,S,_,_]` or similar.
214217

215-
1. `ReusableFn($).{set,mod}State{,IO}`.
218+
1. `ReusableFn($).{set,mod}State`.
216219

217220
You'll find that if you try `ReusableFn($.method)` Scala will fail to infer the correct types.
218221
Use `ReusableFn($).method` instead to get the types that you expect.
219222

220-
Example: instead of `ReusableFn($.setState)` use `ReusableFn($).setState` and you will correctly get a `S ~=> Unit`.
223+
Example: instead of `ReusableFn($.setState)` use `ReusableFn($).setState` and you will correctly get a `S ~=> Callback`.
221224

222225
2. `ReusableFn.endo____`.
223226

224227
Anytime the input to your `ReusableFn` is an endofunction (`A => A`), additional methods starting with `endo` become available.
225228

226-
Specifically, `ReusableFn($).modState` returns a `(S => S) ~=> Unit` which you will often want to transform.
229+
Specifically, `ReusableFn($).modState` returns a `(S => S) ~=> Callback` which you will often want to transform.
227230
These examples would be available on an `(S => S) ~=> U`:
228231

229232
* `endoCall (S => (A => S)): A ~=> U` - Call a 1-arg function on `S`.
@@ -237,19 +240,19 @@ For these examples imagine `$` to be your component's scope instance, eg. `Backe
237240
class Backend($: BackendScope[_, Map[Int, String]]) {
238241

239242
// Manual long-hand
240-
val long: Int ~=> (String ~=> IO[Unit]) =
241-
ReusableFn((id: Int, data: String) => $.modStateIO(map => map.updated(id, data)))
243+
val long: Int ~=> (String ~=> Callback) =
244+
ReusableFn((id: Int, data: String) => $.modState(map => map.updated(id, data)))
242245

243246
// Shorter using helpers described above
244-
val short: Int ~=> (String ~=> IO[Unit]) =
245-
ReusableFn($).modStateIO.endoCall2(_.updated)
247+
val short: Int ~=> (String ~=> Callback) =
248+
ReusableFn($).modState.endoCall2(_.updated)
246249
```
247250

248-
3. `ReusableFn($ focusStateL lens)`
251+
3. `ReusableFn($ zoomL lens)`
249252

250253
Lenses provide an abstraction over read-and-write field access.
251254
Using Monocle, you can annotate your case classes with `@Lenses` to gain automatic lenses.
252-
`$ focusStateL lens` will then narrow the scope of its state to the field targeted by the given lens.
255+
`$ zoomL lens` will then narrow the scope of its state to the field targeted by the given lens.
253256
This can then be used with `ReusableFn` as follows:
254257

255258
```scala
@@ -258,8 +261,8 @@ For these examples imagine `$` to be your component's scope instance, eg. `Backe
258261

259262
class Backend($: BackendScope[_, Person]) {
260263

261-
val nameSetter: String ~=> Unit =
262-
ReusableFn($ focusStateL Person.name).setState
264+
val nameSetter: String ~=> Callback =
265+
ReusableFn($ zoomL Person.name).setState
263266
```
264267

265268

@@ -322,28 +325,23 @@ there is also `ReusableVar`.
322325
@Lenses case class State(name: String, desc: String)
323326

324327
val topComponent = ReactComponentB[State]("Demo")
325-
.getInitialState(identity)
326-
.backend(new Backend(_))
327-
.render(_.backend.render)
328-
.build
329-
330-
class Backend($: BackendScope[State, State]) {
331-
val setName = ReusableVar.state($ focusStateL State.name)
332-
val setDesc = ReusableVar.state($ focusStateL State.desc)
328+
.initialState_P(identity)
329+
.renderP { ($, p) =>
330+
val setName = ReusableVar.state($ zoomL State.name)
331+
val setDesc = ReusableVar.state($ zoomL State.desc)
333332

334-
def render =
335333
<.div(
336334
stringEditor(setName),
337335
stringEditor(setDesc))
338-
}
336+
}
337+
.build
339338

340-
val stringEditor = ReactComponentB[ReusableVar[String]]("StringEditor")
341-
.stateless
342-
.render((p, _) =>
339+
lazy val stringEditor = ReactComponentB[ReusableVar[String]]("StringEditor")
340+
.render_P(p =>
343341
<.input(
344342
^.`type` := "text",
345343
^.value := p.value,
346-
^.onChange ~~> ((e: ReactEventI) => p.set(e.target.value))))
344+
^.onChange ==> ((e: ReactEventI) => p.set(e.target.value))))
347345
.configure(Reusability.shouldComponentUpdate)
348346
.build
349347
```
@@ -431,10 +429,20 @@ Create a non-derivative `Px` using one of these:
431429
}
432430
```
433431

434-
4. `Px.const(A)` & `Px.lazyConst(=> A)` - A constant value.
432+
4. `Px.bs($).{props,state}{A,M}` - A value extracts from a component's backend's props or state.
433+
434+
The `A` suffix denotes "Auto refresh", meaning that the function will be called every time the value is requested, and the value updated if necessary.<br>
435+
The `M` suffix denotes "Manual refresh", meaning you must call `.refresh` yourself to check for updates.
436+
437+
5. `Px.const(A)` & `Px.lazyConst(=> A)` - A constant value.
435438

436439
These `Px`s do not have the ability to change.
437440

441+
6. `Px.cb{A,M}(CallbackTo[A])` - A value which is the result of running a callback.
442+
443+
The `A` suffix denotes "Auto refresh", meaning that the function will be called every time the value is requested, and the value updated if necessary.<br>
444+
The `M` suffix denotes "Manual refresh", meaning you must call `.refresh` yourself to check for updates.
445+
438446

439447
#### Derivative instances
440448
Derivative `Px`s are created by:
@@ -445,8 +453,8 @@ Derivative `Px`s are created by:
445453

446454
Example:
447455
```scala
448-
val project : Px[Project] = Px.thunkM($.props)
449-
val viewSettings: Px[ViewSettings] = Px.thunkM($.state.viewSettings)
456+
val project : Px[Project] = Px.bs($).propsM
457+
val viewSettings: Px[ViewSettings] = Px.bs($).stateM(_.viewSettings)
450458

451459
// Using .map
452460
val columns : Px[Columns] = viewSettings.map(_.columns)

doc/ROUTER.md

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,14 @@ Here a subset of useful methods. Use IDE auto-complete or check the source for t
346346
```
347347

348348
* `set___` - Programmatically navigate to route when invoked.
349-
* `set(Page): IO[Unit]` - Return a procedure that will navigate to route.
350-
* `setEH(Page): ReactEvent => IO[Unit]` - Consume an event and set the route.
351-
* `setOnClick(Page): TagMod` - Set the route when the subject is clicked.<br>Shorthand for `^.onClick ~~> ctl.setEH(page)`.
349+
* `set(Page): Callback` - Return a procedure that will navigate to route.
350+
* `setEH(Page): ReactEvent => Callback` - Consume an event and set the route.
351+
* `setOnClick(Page): TagMod` - Set the route when the subject is clicked.<br>Shorthand for `^.onClick ==> ctl.setEH(page)`.
352352

353-
* `refresh: IO[Unit]` - Refresh the current route when invoked.
353+
* `refresh: Callback` - Refresh the current route when invoked.
354354

355355
```scala
356-
^.button("Refresh", ^.onClick ~~> ctl.refresh)
356+
^.button("Refresh", ^.onClick --> ctl.refresh)
357357
```
358358

359359
* `urlFor(Page): AbsUrl` - Get the absolute URL of a given page.
@@ -397,14 +397,12 @@ When the condition is met, the route is usable; when unmet, a fallback behaviour
397397
* If response is `None` it will be as if this rule doesn't exist and will likely end
398398
* in the route-not-found fallback behaviour.
399399
*/
400-
def addCondition(cond: => Boolean)(condUnmet: Page => Option[Action[Page]]): Rule[Page]
401-
402-
def addConditionIO(cond: IO[Boolean])(condUnmet: Page => Option[Action[Page]]): Rule[Page]
400+
def addCondition(cond: CallbackTo[Boolean])(condUnmet: Page => Option[Action[Page]]): Rule[Page]
403401
```
404402

405403
Example:
406404
```scala
407-
def grantPrivateAccess: Boolean =
405+
def grantPrivateAccess: CallbackB =
408406
???
409407

410408
val privatePages = (emptyRule
@@ -416,7 +414,7 @@ val privatePages = (emptyRule
416414

417415
### Rendering with a layout
418416

419-
Once you have a `RouterConfig`, you can call `.renderWith` on it to supply your own render function that will be invokved each time a route is rendered. It takes a function in the shape: `(RouterCtl[Page], Resolution[Page]) => ReactElement` where a `Resolution` is:
417+
Once you have a `RouterConfig`, you can call `.renderWith` on it to supply your own render function that will be invoked each time a route is rendered. It takes a function in the shape: `(RouterCtl[Page], Resolution[Page]) => ReactElement` where a `Resolution` is:
420418

421419
```scala
422420
/**
@@ -440,26 +438,24 @@ Out-of-the-box, the default action is to scroll the window to the top.
440438
You can *add* your own actions by calling `.onPostRender` on your `RouterConfig` instance.
441439
You can *set* the entire callback (i.e. override instead of add) using `.setPostRender`.
442440

443-
Both `.onPostRender` and `.setPostRender` take a single arg: `(Option[Page], Page) => IO[Unit]`.
441+
Both `.onPostRender` and `.setPostRender` take a single arg: `(Option[Page], Page) => Callback`.
444442
The function is provided the previously-rendered page (or `None` when a router renders its first page),
445443
and the current page.
446444

447445
Example:
448446
```scala
449-
import scalaz.effect.IO
450-
451447
val routerConfig = RouterConfigDsl[Page].buildConfig { dsl =>
452448
import dsl._
453449
( staticRoute(root, Home) ~> render(???)
454450
| staticRoute("#about", About) ~> render(???)
455451
)
456452
.notFound(???)
457-
.onPostRender((prev, cur) => // ← available after .notFound()
458-
IO(println(s"Page changing from $prev to $cur."))) // ← our callback
453+
.onPostRender((prev, cur) => // ← available after .notFound()
454+
Callback.log(s"Page changing from $prev to $cur.")) // ← our callback
459455
}
460456
```
461457

462-
*Hey? Why wrap in `IO()`?*
458+
*Hey? Why wrap in `Callback`?*
463459

464460
It gives you a guarantee by me and the Scala compiler that whatever you put inside,
465461
will only be executed in a callback. Underlying code won't accidently call it now when *installing* the callback,

0 commit comments

Comments
 (0)