aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/03-controlled-inputs/README.md21
-rw-r--r--examples/03-controlled-inputs/src/app.gleam15
-rw-r--r--examples/04-custom-event-handlers/README.md4
-rw-r--r--examples/04-custom-event-handlers/src/app.gleam12
-rw-r--r--examples/05-http-requests/README.md74
-rw-r--r--examples/05-http-requests/src/app.gleam16
-rw-r--r--examples/06-custom-effects/README.md12
-rw-r--r--examples/06-custom-effects/src/app.gleam24
8 files changed, 122 insertions, 56 deletions
diff --git a/examples/03-controlled-inputs/README.md b/examples/03-controlled-inputs/README.md
index 30fda75..95ca620 100644
--- a/examples/03-controlled-inputs/README.md
+++ b/examples/03-controlled-inputs/README.md
@@ -18,8 +18,9 @@ two things:
ui.input([
// Input's value is fixed to the model's `value` field
attribute.value(model.value),
- // Whenever the input changes, we send a `GotInput` message with the new value
- event.on_input(GotInput)
+ // Whenever the input changes, we send a `UserUpdatedMessage` message with the
+ // new value
+ event.on_input(UserUpdatedMessage)
])
```
@@ -36,7 +37,7 @@ value is less than 10 characters long.
```gleam
case msg {
- GotInput(value) -> {
+ UserUpdatedMessage(value) -> {
let length = string.length(value)
case length <= model.max {
@@ -48,6 +49,20 @@ case msg {
...
```
+## A note on message naming
+
+In our [state management guide](https://hexdocs.pm/lustre/guide/02-state-management.html)
+we touch on the idea of "messages not actions." We think the best way to name your
+messages is following a "Subject Verb Object" pattern: `UserUpdatedMessage` not
+`SetMessage` and so on.
+
+This approach to message naming can feel a cumbersome at first, especially for
+small examples like this. One of Lustre's super powers is that as your app grows
+in size, your `Msg` type becomes a very helpful overview of all the different
+events your app can handle. When they take the form of `Subject Verb Object` it
+gives you an immediate sense of the different things that speak to your app: how
+much is coming from your backend, how much is user input, and so on.
+
## Getting help
If you're having trouble with Lustre or not sure what the right way to do
diff --git a/examples/03-controlled-inputs/src/app.gleam b/examples/03-controlled-inputs/src/app.gleam
index d3c764e..f620e6a 100644
--- a/examples/03-controlled-inputs/src/app.gleam
+++ b/examples/03-controlled-inputs/src/app.gleam
@@ -35,13 +35,13 @@ fn init(_) -> Model {
// UPDATE ----------------------------------------------------------------------
pub opaque type Msg {
- GotInput(value: String)
- Reset
+ UserUpdatedMessage(value: String)
+ UserResetMessage
}
fn update(model: Model, msg: Msg) -> Model {
case msg {
- GotInput(value) -> {
+ UserUpdatedMessage(value) -> {
let length = string.length(value)
case length <= model.max {
@@ -49,7 +49,7 @@ fn update(model: Model, msg: Msg) -> Model {
False -> model
}
}
- Reset -> Model(..model, value: "", length: 0)
+ UserResetMessage -> Model(..model, value: "", length: 0)
}
}
@@ -67,10 +67,13 @@ fn view(model: Model) -> Element(Msg) {
ui.field(
[],
[element.text("Write a message:")],
- ui.input([attribute.value(model.value), event.on_input(GotInput)]),
+ ui.input([
+ attribute.value(model.value),
+ event.on_input(UserUpdatedMessage),
+ ]),
[element.text(length <> "/" <> max)],
),
- ui.button([event.on_click(Reset)], [element.text("Reset")]),
+ ui.button([event.on_click(UserResetMessage)], [element.text("Reset")]),
),
)
}
diff --git a/examples/04-custom-event-handlers/README.md b/examples/04-custom-event-handlers/README.md
index a2a09c5..434725a 100644
--- a/examples/04-custom-event-handlers/README.md
+++ b/examples/04-custom-event-handlers/README.md
@@ -74,7 +74,3 @@ In this [example code](./src/app.gleam#L63), we define a custom input handler ca
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).
-
-While our docs are still a work in progress, the official [Elm guide](https://guide.elm-lang.org)
-is also a great resource for learning about the Model-View-Update architecture
-and the kinds of patterns that Lustre is built around.
diff --git a/examples/04-custom-event-handlers/src/app.gleam b/examples/04-custom-event-handlers/src/app.gleam
index c3f5d0b..011b09c 100644
--- a/examples/04-custom-event-handlers/src/app.gleam
+++ b/examples/04-custom-event-handlers/src/app.gleam
@@ -37,20 +37,20 @@ fn init(_) -> Model {
// UPDATE ----------------------------------------------------------------------
pub opaque type Msg {
- GotInput(value: String)
- Reset
+ UserUpdatedMessage(value: String)
+ UserResetMessage
}
fn update(model: Model, msg: Msg) -> Model {
case msg {
- GotInput(value) -> {
+ UserUpdatedMessage(value) -> {
let length = string.length(value)
case length <= model.max {
True -> Model(..model, value: value, length: length)
False -> model
}
}
- Reset -> Model(..model, value: "", length: 0)
+ UserResetMessage -> Model(..model, value: "", length: 0)
}
}
@@ -66,7 +66,7 @@ fn view(model: Model) -> Element(Msg) {
let loud = string.uppercase(value)
- Ok(GotInput(loud))
+ Ok(UserUpdatedMessage(loud))
}
ui.centre(
@@ -79,7 +79,7 @@ fn view(model: Model) -> Element(Msg) {
ui.input([attribute.value(model.value), event.on("input", make_it_loud)]),
[element.text(length <> "/" <> max)],
),
- ui.button([event.on_click(Reset)], [element.text("Reset")]),
+ ui.button([event.on_click(UserResetMessage)], [element.text("Reset")]),
),
)
}
diff --git a/examples/05-http-requests/README.md b/examples/05-http-requests/README.md
index 3aacfe5..4a118e4 100644
--- a/examples/05-http-requests/README.md
+++ b/examples/05-http-requests/README.md
@@ -2,25 +2,28 @@
# 05 HTTP Requests
-Up until now, all the logic in our examples has run neatly in a self-contained `Init -> Update 🔁 View` loop. But our applications often need to interact with the outside world, whether through browser APIs or HTTP requests.
-
-Up until now, we've seen Lustre applications constructed with the `lustre.simple`
-constructor. These kinds of applications are great for introducing the Model-View-Update
-(MVU) pattern, but for most real-world applications we'll need a way to talk to
-the outside world.
+In the previous examples, we've seen Lustre applications constructed with the
+[`lustre.simple`](https://hexdocs.pm/lustre/lustre.html#simple) constructor.
+These kinds of applications are great for introducing the Model-View-Update (MVU)
+pattern, but for most real-world applications we'll need a way to talk to the
+outside world.
Lustre's runtime includes _managed effects_, which allow us to perform side effects
like HTTP requests and communicate the results back to our application's `update`
function. To learn more about Lustre's effect system and why it's useful, check
out the [side effects guide](https://hexdocs.pm/lustre/guide/side-effects.html),
-or the docs for the [lustre/effect module](https://hexdocs.pm/lustre/lustre/effect.html)
-For now, we will focus on how to send HTTP requests in a Lustre application: a
-pretty important thing to know!
+or the docs for the [`lustre/effect` module](https://hexdocs.pm/lustre/lustre/effect.html).
+
+This example is a practical look at what effects mean in Lustre, and we'll look
+at how to send HTTP requests in a Lustre application: a pretty important thing to
+know!
## Moving on from `lustre.simple`
-From now on, the rest of these examples will use a different application constructor:
-[`lustre.application`]. Let's compare the type of both functions:
+From this example onwards, we will use a new application constructor:
+[`lustre.application`](https://hexdocs.pm/lustre/lustre.html#application). Full Lustre
+applications have the ability to communicate to the runtime. Let's compare the type
+of both the `simple` and `application` functions:
```gleam
pub fn simple(
@@ -70,7 +73,7 @@ fn get_quote() -> Effect(Msg) {
dynamic.field("content", dynamic.string),
)
- lustre_http.get(url, lustre_http.expect_json(decoder, GotQuote))
+ lustre_http.get(url, lustre_http.expect_json(decoder, ApiUpdatedQuote))
}
```
@@ -80,20 +83,55 @@ To construct HTTP requests, we need a few different things:
- A description of what we _expect_ the result to be. There are a few options:
`expect_anything`, `expect_text`, `expect_json`. In this example we say we're
- expecting a JSON response and provide a decode.
+ expecting a JSON response and provide a decoder.
-- A long with what we expect the response to be, we also need to provide a way
+- Along with what we expect the response to be, we also need to provide a way
to turn that response into a `Msg` value that our `update` function can handle.
The same applies for post requests too, but there you also need to provide the
JSON body of the request.
+## Tying it together
+
+We now have a function that can create an `Effect` for us, but we need to hand it
+to the runtime to be executed. The only way we can do that is by returning it from
+our `update` (or `init`) function! We attach an event listener on a button, and
+when the user clicks that button we'll return the `Effect` we want to perform as
+the second element of a tuple:
+
+```gleam
+fn view(model: Model) -> Element(Msg) {
+ ui.centre([],
+ ui.button([event.on_click(UserClickedRefresh)], [
+ element.text("New quote"),
+ ]),
+ )
+}
+
+fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
+ case msg {
+ UserClickedRefresh -> #(model, get_quote())
+ ...
+ }
+}
+```
+
+Of course, we need to handle responses from the quote API in our `update` function
+too. When there are no side effects we want the runtime to perform for us, we need
+to call `effect.none()`:
+
+```gleam
+fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
+ case msg {
+ ...
+ ApiUpdatedQuote(Ok(quote)) -> #(Model(quote: Some(quote)), effect.none())
+ ApiUpdatedQuote(Error(_)) -> #(model, effect.none())
+ }
+}
+```
+
## 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).
-
-While our docs are still a work in progress, the official [Elm guide](https://guide.elm-lang.org)
-is also a great resource for learning about the Model-View-Update architecture
-and the kinds of patterns that Lustre is built around.
diff --git a/examples/05-http-requests/src/app.gleam b/examples/05-http-requests/src/app.gleam
index 04e0af6..9442cf3 100644
--- a/examples/05-http-requests/src/app.gleam
+++ b/examples/05-http-requests/src/app.gleam
@@ -45,15 +45,15 @@ fn init(_) -> #(Model, Effect(Msg)) {
// UPDATE ----------------------------------------------------------------------
pub opaque type Msg {
- Refresh
- GotQuote(Result(Quote, HttpError))
+ UserClickedRefresh
+ ApiUpdatedQuote(Result(Quote, HttpError))
}
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
case msg {
- Refresh -> #(model, get_quote())
- GotQuote(Ok(quote)) -> #(Model(quote: Some(quote)), effect.none())
- GotQuote(Error(_)) -> #(model, effect.none())
+ UserClickedRefresh -> #(model, get_quote())
+ ApiUpdatedQuote(Ok(quote)) -> #(Model(quote: Some(quote)), effect.none())
+ ApiUpdatedQuote(Error(_)) -> #(model, effect.none())
}
}
@@ -66,7 +66,7 @@ fn get_quote() -> Effect(Msg) {
dynamic.field("content", dynamic.string),
)
- lustre_http.get(url, lustre_http.expect_json(decoder, GotQuote))
+ lustre_http.get(url, lustre_http.expect_json(decoder, ApiUpdatedQuote))
}
// VIEW ------------------------------------------------------------------------
@@ -79,7 +79,9 @@ fn view(model: Model) -> Element(Msg) {
ui.aside(
[aside.min_width(70), attribute.style([#("width", "60ch")])],
view_quote(model.quote),
- ui.button([event.on_click(Refresh)], [element.text("New quote")]),
+ ui.button([event.on_click(UserClickedRefresh)], [
+ element.text("New quote"),
+ ]),
),
)
}
diff --git a/examples/06-custom-effects/README.md b/examples/06-custom-effects/README.md
index f8ad49f..fe6822b 100644
--- a/examples/06-custom-effects/README.md
+++ b/examples/06-custom-effects/README.md
@@ -5,6 +5,18 @@ bit about Lustre or Elm and want to help out, we'd love to have your help! Pleas
[open an issue](https://github.com/lustre-labs/lustre/issues/new) if you have any
ideas or reach out to @hayleigh.dev on the [Gleam discord](https://discord.gg/Fm8Pwmy).
+## 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
diff --git a/examples/06-custom-effects/src/app.gleam b/examples/06-custom-effects/src/app.gleam
index 5399903..e04484a 100644
--- a/examples/06-custom-effects/src/app.gleam
+++ b/examples/06-custom-effects/src/app.gleam
@@ -28,34 +28,34 @@ type Model {
}
fn init(_) -> #(Model, Effect(Msg)) {
- #(Model(message: None), read_localstorage("message", GotMessage))
+ #(Model(message: None), read_localstorage("message"))
}
// UPDATE ----------------------------------------------------------------------
pub opaque type Msg {
- GotInput(String)
- GotMessage(Result(String, Nil))
+ UserUpdatedMessage(String)
+ CacheUpdatedMessage(Result(String, Nil))
}
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
case msg {
- GotInput(input) -> #(
+ UserUpdatedMessage(input) -> #(
Model(message: Some(input)),
write_localstorage("message", input),
)
- GotMessage(Ok(message)) -> #(Model(message: Some(message)), effect.none())
- GotMessage(Error(_)) -> #(model, effect.none())
+ CacheUpdatedMessage(Ok(message)) -> #(
+ Model(message: Some(message)),
+ effect.none(),
+ )
+ CacheUpdatedMessage(Error(_)) -> #(model, effect.none())
}
}
-fn read_localstorage(
- key: String,
- to_msg: fn(Result(String, Nil)) -> msg,
-) -> Effect(msg) {
+fn read_localstorage(key: String) -> Effect(Msg) {
effect.from(fn(dispatch) {
do_read_localstorage(key)
- |> to_msg
+ |> CacheUpdatedMessage
|> dispatch
})
}
@@ -85,7 +85,7 @@ fn view(model: Model) -> Element(Msg) {
ui.field(
[],
[],
- ui.input([attribute.value(message), event.on_input(GotInput)]),
+ ui.input([attribute.value(message), event.on_input(UserUpdatedMessage)]),
[element.text("Type a message and refresh the page")],
),
)