1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|

# 06 Custom Effects
In the last example, we showed how to use effects provided by `lustre_http`. In
this example we'll see how to create effects of our own using Lustre's
[`effect.from`](https://hexdocs.pm/lustre/lustre/effect.html#from)
function.
Since we use effects to communicate with _external_ systems (like the browser or
the Erlang VM) you'll find creating custom effects usually involves Gleam's
[external functions](https://tour.gleam.run/everything/#advanced-features-externals).
So be sure to read up on that!
> Gleam externals are part of its "foreign function interface", which is why
> you'll typically see files with `ffi` in the name - like
> [`app.ffi.mjs`](./src/app.ffi.mjs).
## Accessing Browser Storage
In this example, the external system we want to interact with is the browser's
[local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
This way, we can write a message into the text input and it will still be there
when we refresh the page. Handy!
The `view`, `update` and `init` functions should look pretty familiar by now, so
let's focus on the functions that generate our custom effects.
```rust
fn read_localstorage(key: String) -> Effect(Msg) {
effect.from(fn(dispatch) {
do_read_localstorage(key)
|> CacheUpdatedMessage
|> dispatch
})
}
```
We use `effect.from` by passing it a custom function that describes the effect
we want the Lustre runtime to perform. Our custom function will receive a
`dispatch` function as its only parameter - we can use that to send new messages
back to our `update` function if we want to.
In this case, we read from local storage, pipe its value into the
`CacheUpdatedMessage` constructor, and pipe that to the `dispatch` function so
our `update` messsage can handle it.
```rust
fn write_localstorage(key: String, value: String) -> Effect(msg) {
effect.from(fn(_) {
do_write_localstorage(key, value)
})
}
```
Here, our effect is simpler. We tell the gleam compiler we don't need to use the
`dispatch` function by replacing it with a [discard
pattern](https://tour.gleam.run/everything/#basics-discard-patterns). Then we
write to local storage, and no more work needs to be done.
You may be wondering, why bother using an effect if we aren't also going to update
our program with the result? Why not just fire off `do_write_localstorage` directly
from the `update` function or a custom event handler?
Using effects has many benefits! It lets us implement our `update` and `view`
functions as [pure functions](https://github.com/lustre-labs/lustre/blob/main/pages/hints/pure-functions.md).
This makes them easier to test, allows for time-travel debugging, and even opens
the door to easily porting them to [server components](https://hexdocs.pm/lustre/lustre/server_component.html).
## Another note on message naming
In our [controlled inputs
example](https://github.com/lustre-labs/lustre/tree/main/examples/03-controlled-inputs)
we touched on the idea of naming messages in a "Subject Verb Object" pattern.
This example neatly shows the benefits of taking such an approach once different
"things" start talking to your application.
It would be easy to have a single `SetMessage` variant that both the user input
and local storage lookup use to update the model, but doing so might encourage
us to conceal the fact that the local storage lookup can fail and makes it
harder to see what things our app deals with.
## Getting help
If you're having trouble with Lustre or not sure what the right way to do
something is, the best place to get help is the [Gleam Discord
server](https://discord.gg/Fm8Pwmy). You could also open an issue on the [Lustre
GitHub repository](https://github.com/lustre-labs/lustre/issues).
|