Skip to content

Commit 1ba7eb5

Browse files
authored
Update CALLBACK.md
1 parent 092e8ca commit 1ba7eb5

File tree

1 file changed

+101
-35
lines changed

1 file changed

+101
-35
lines changed

doc/CALLBACK.md

Lines changed: 101 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,109 @@
11
Callback
22
========
33

4+
The `Callback` class encapsulates logic and side-effects that are meant to be *executable by React*, when/if React chooses to execute it. Examples are React responding to the user clicking a button, or React unmounting a component.
5+
6+
**WARNING**: If you're new to scalajs-react and typed effects (or just functional programming in general), then it's important you read this because if you incorrectly mix this with imperative-style code that performs side-effects, you'll likely have runtime bugs where data goes stale and/or changes go undetected.
7+
8+
#### Contents
9+
- [Introduction](#introduction)
10+
- [Utilities once you have a `Callback`](#utilities-once-you-have-a-callback)
11+
- [Composition](#composition)
12+
- [Monadic Learning Curve](#monadic-learning-curve)
13+
- [Manual Execution](#manual-execution)
14+
15+
Introduction
16+
============
17+
418
A callback is a procedure that is:
5-
* meant to be run by an event handler or React lifecycle method (as opposed to on the main thread or in a `render` method).
6-
* repeatable. It can be run more than once.
7-
* Is pure (does nothing) in its construction. If you create a `Callback` but never run it, no action or effects should occur.
19+
* Executable by React when/if it chooses (usually an event handler or React lifecycle method.
20+
* Executed asychronously by React.
21+
* Repeatable. It can be run more than once.
22+
* Pure (does nothing) when you create an instance. If you create a `Callback` but never run it, no action or effects should occur.
823

924
Callbacks are represented by:
1025
* `Callback` which doesn't return a result.
1126
* `CallbackTo[A]` which returns an `A`.
1227

28+
Intuitively, you can think of a `CallbackTo[A]` as `() => A`, and a `Callback` as a `() => Unit`.
29+
1330
Actually `Callback` is `CallbackTo[Unit]` with a different companion object, full of different goodies that all return `Unit`.
1431

15-
You can create callbacks in a number of ways:
1632

17-
* By wrapping your code:
33+
Creation
34+
========
1835

19-
```scala
20-
// This is Callback. It is also a CallbackTo[Unit].
21-
Callback{ println("Hello! I'll be executed later.") }
36+
There are a number of ways in which to create a callback.
2237

23-
// This is a CallbackTo[Int].
24-
CallbackTo(123)
25-
```
38+
The simplest is just to surround all of your code in `Callback{ ... }` or `CallbackTo{ ... }`.
39+
Example:
40+
```scala
41+
// This is Callback. It is also a CallbackTo[Unit].
42+
val sayHello = Callback {
43+
println("Hello, I'll be executed asynchronously.")
44+
println("Bye!")
45+
}
46+
```
2647

27-
* When your component modifies its state via `.setState` or `.modState`, you are provided a `Callback` for the operation.
48+
Any impure logic/effects (such as accessing DOM state, AAJX, or global variables), *must be inside* the Callback.
49+
Example:
50+
```scala
51+
object Auth {
52+
private var _isUserLoggedIn = false
2853

29-
```scala
30-
componentScope.modState(_.copy(name = newName)) // returns a Callback
31-
```
54+
val isUserLoggedIn: CallbackTo[Boolean] =
55+
CallbackTo(_isUserLoggedIn)
3256

33-
* Using one of the `Callback` object convenience methods
57+
// Callback = CallbackTo[Unit] = no result
58+
val login: Callback =
59+
Callback(_isUserLoggedIn = true)
60+
}
61+
```
3462

35-
```scala
36-
// Convenience for calling `dom.console.log`.
37-
Callback.log("Hello Console reader.")
63+
Another way to create a `Callback` is by calling `.setState` or `.modState` on a component.
64+
The result of these methods is already a `Callback` because a component's state is only changed when React responds to some kind of
65+
event; it's an error to sychronously update the state in a component's `render` method, for example.
66+
```scala
67+
myComponent.setState(Person(1001, "Bob")) // returns a Callback
68+
```
3869

39-
// Provides both compile-time and runtime warnings that a callback isn't implemented yet.
40-
Callback.TODO("AJAX not implemented yet")
70+
There are also convenience methods in the `Callback` object:
71+
```scala
72+
// Convenience for calling `dom.console.log`.
73+
Callback.log("Hello Console reader.")
74+
75+
// Provides both compile-time and runtime warnings that a callback isn't implemented yet.
76+
Callback.TODO("AJAX not implemented yet")
77+
78+
// Return a pure value without doing anything
79+
CallbackTo.pure(0)
80+
```
4181

42-
// Return a pure value without doing anything
43-
CallbackTo.pure(0)
44-
```
82+
Utilities once you have a `Callback`
83+
====================================
4584

46-
`Callback` also has all kinds of useful methods and combinators. Examples:
47-
* Join callbacks together with many methods like `map`, `flatMap`, `tap`, `flatTap`, and all the squigglies that
48-
you may be used to in Haskell and inspired libraries like `*>`, `<*`, `>>`, `<<`, `>>=`, etc.
85+
`Callback` instances come with a bunch of useful utility methods:
4986
* `.attempt` to catch any error in the callback and handle it.
5087
* `.async`/`.delay(n)` to run asynchronously and return a `Future`.
5188
* `.logResult` to print the callback result before returning it.
5289
* `.logDuration` to measure and log how long the callback takes.
90+
* `.map` (as you would expect) to transform the result.
91+
* `.when`/`.unless` to add a condition so that sometimes the callback will be ignored. Like an `if` statement.
92+
* More; see: [Callback.scala](../core/src/main/scala/japgolly/scalajs/react/Callback.scala).
5393

54-
There are other useful methods not listed here.
55-
<br>Have a brief look through the source:
56-
[Callback.scala](../core/src/main/scala/japgolly/scalajs/react/Callback.scala).
5794

58-
Once you have a `Callback` you can run it manually if you need, by calling `.runNow()`.
59-
It isn't necessary and you shouldn't do it because scalajs-react handles it for you to ensure things run at the right time
60-
on the right threads, but if you ever want to, you can.
95+
Composition
96+
===========
6197

62-
#### Fusion via `>>`
98+
When you want to compose multiple `Callback` instances, there are many ways depending on what specifically you want to do.
99+
* *(Most-common)* `.flatMap` and/or for-comprehensions. Same as using Scala's `Future`.
100+
* *(Most-common)* `>>`. This operator composes callbacks sequentially. i.e. `a >> b >> c` will create a new callback which will execute `a` first, then `b` second, and finally `c` third.
101+
* If you want to compose more than two callbacks, or don't know how many you'll have at runtime, there is `Callback.sequence` and `Callback.traverse`.
102+
* Monadic and applicative ops that'd you'd expect coming from languages like Haskell are there (`*>`, `<*`, `>>`, `<<`, `>>=`, etc). They're baked in rather than typeclass-provided.
63103

64104
The `>>` operator deserves a special mention as it's commonly useful.
65105
It's used to fuse to callbacks together sequentially.
66-
It's like a pure version of `;` which is how you sequence statements imperatively.
106+
It's like a pure version of `;` which is how you sequence statements imperatively (i.e. `doThis(); doThat()` becomes `doThis >> doThat`).
67107

68108
```scala
69109
def greet: Callback =
@@ -87,6 +127,15 @@ def greet: Callback = {
87127
val bye = Callback(println("Goodbye."))
88128
hello.flatMap(_ => bye)
89129
}
130+
131+
// and again, equivalent to this ↓
132+
133+
def greet: Callback =
134+
for {
135+
_ <- Callback(println("Hello."))
136+
_ <- Callback(println("Goodbye."))
137+
} yield ()
138+
}
90139
```
91140

92141
If you're wondering why `>>`, it's a convention used in various other monadic libraries.
@@ -170,3 +219,20 @@ Notice that we change `speak()` into `speak` as it's now pure.
170219
It's actually recommended that you *not* take this approach and instead, use proper operators to combine callbacks as the compiler will be able to offer help and catch problems.
171220

172221

222+
Manual Execution
223+
================
224+
225+
As is stressed above, `Callback`s are meant to be executed by React at a time and frequency of its choosing.
226+
227+
There are scenarios in which you may want to execute a callback manually:
228+
* Working with an external service.
229+
* In an AJAX callback.
230+
* In a websocket callback.
231+
* In a unit test.
232+
233+
There are two ways to go about this:
234+
235+
1. Execute the `Callback` yourself by calling `.runNow()`.
236+
2. Ask `scalajs-react` for an interface that performs the side-effects directly instead of returning `Callback`s. See https://japgolly.github.io/scalajs-react/#examples/websockets for a demo.
237+
238+
Reminder: You should never do this in a React context.

0 commit comments

Comments
 (0)