Skip to content

Commit d5521f1

Browse files
authored
Merge pull request #331 from japgolly/japgolly-patch-1
Add a common-mistakes section to Callback doc
2 parents 1ba7eb5 + 0316a56 commit d5521f1

File tree

1 file changed

+60
-0
lines changed

1 file changed

+60
-0
lines changed

doc/CALLBACK.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The `Callback` class encapsulates logic and side-effects that are meant to be *e
1111
- [Composition](#composition)
1212
- [Monadic Learning Curve](#monadic-learning-curve)
1313
- [Manual Execution](#manual-execution)
14+
- [Common Mistakes](#common-mistakes)
1415

1516
Introduction
1617
============
@@ -236,3 +237,62 @@ There are two ways to go about this:
236237
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.
237238

238239
Reminder: You should never do this in a React context.
240+
241+
242+
Common Mistakes
243+
===============
244+
245+
* **Executing instead of composing.**
246+
247+
Callbacks must do nothing when you first create them; React might never even call them.
248+
If you call `.runNow()` when you're creating a `Callback` that's a bug because you're forcing a one-time execution during construction.
249+
250+
If fact, even the `{` in `def increment(): Callback = {` is a code-smell. Either use `(` or nothing at all.
251+
252+
BROKEN:
253+
```scala
254+
def increment(): Callback = {
255+
$.modState(_ + 1).runNow()
256+
Callback.log("Incremented count by 1")
257+
}
258+
```
259+
260+
FIXED:
261+
```scala
262+
val increment: Callback =
263+
$.modState(_ + 1) >>
264+
Callback.log("Incremented count by 1")
265+
```
266+
267+
* **Side-effects (especially accessing mutable state) during construction**
268+
269+
Callbacks must be repeatable.
270+
If you perform a side-effect during construction then it happens exactly once (instead of repeatedly) and at the wrong time (construction instead of callback execution).
271+
If during construction, you read mutable state like a global variable then it is read exactly once and at the wrong time too, which will give the impression at runtime that the state is never updated.
272+
273+
Make sure anything impure is *inside* the callback.
274+
275+
BROKEN:
276+
```scala
277+
def grantPrivateAccess = {
278+
val rule: js.Any = global.window.localStorage.getItem("token")
279+
CallbackTo(rule != null)
280+
}
281+
```
282+
283+
FIXED:
284+
```scala
285+
val grantPrivateAccess = CallbackTo {
286+
val rule: js.Any = global.window.localStorage.getItem("token")
287+
rule != null
288+
}
289+
```
290+
291+
Even better is to isolate the impurity and make it DRY.
292+
```scala
293+
val getToken: CallbackTo[js.Any] =
294+
CallbackTo(global.window.localStorage.getItem("token"))
295+
296+
val grantPrivateAccess: CallbackTo[Boolean] =
297+
getToken.map(_ != null)
298+
```

0 commit comments

Comments
 (0)