aboutsummaryrefslogtreecommitdiff
path: root/docs/public/page/api/lustre.md
blob: 7ec50216673c09e513c2edb035b356dd40e051cc (plain)
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# lustre

## Applications

### App | erlang javascript

```gleam
pub type App(flags, model, msg)
```

The `App` type represents all the parts that make up a Lustre program in the
Model-View-Update architecture along with the runtime necessary to run it.

Although the type itself is exposed to both the Erlang and JavaScript targets,
the functions in this module to construct an `App` are only available in the
JavaScript target, and `start` will only succeed when ran in the browser.

In the future we may have a way to run Lustre applications on the backend, if
you have any ideas on how to achieve this I'd love to hear about them!

### Error | erlang javascript

```gleam
pub type Error {
  AppAlreadyStarted
  AppNotYetStarted
  BadComponentName
  ComponentAlreadyRegistered
  ElementNotFound
  NotABrowser
}
```

The `Error` type represents all the ways that a Lustre program can fail. These
include things like trying to start an application that has already been started,
registering a component with a name that is not valid, or trying to start an
application in a context that is not a browser.

Often you will want to perform a couple of these actions together, and unifying
the error type makes this easy. In many of the examples we `let assert` that the
result is `Ok` but if you wanted to be a bit more dilligent you might use
`result.try` instead:

```gleam
import gleam/result
import lustre

pub fn main () {
  use _ <- result.try(lustre.component("my-component", ...))
  let app = lustre.application(...)
  use dispatch <- result.try(lustre.start(app, "[data-lustre-app]", Nil))

  ...
}
```

### element | javascript

```gleam
pub fn element(el: Element(msg)) -> App(Nil, Nil, msg)
```

An `element` application is the simplest kind of Lustre program. It takes an
`Element` to render and renders it to the DOM. These applications hold no state
and do not respond to messages, but that doesn't mean they are not interactive!

It is possible for [`components`](#component) to be rendered inside an
`element` application, and these components can be interactive with their own
contained state and update loops.

### simple | javascript

```gleam
pub fn simple(
  init: fn(flags) -> model,
  update: fn(model, msg) -> model,
  view: fn(model) -> Element(msg)
) -> App(flags, model, msg)
```

A `simple` program introduces the Model-View-Update architecture but leaves out
the ability to dispatch side effects. This means your programs are interactive
but cannot talk to the outside world.

### application | javascript

```gleam
pub fn application(
  init: fn(flags) -> #(model, Effect(msg)),
  update: fn(model, msg) -> #(model, Effect(msg)),
  view: fn(model) -> Element(msg)
) -> App(flags, model, msg)
```

The `application` constructor is the most complete way to build a Lustre app. As
with [`simple`](#simple) it uses the Model-View-Update architecture, but now your
init and update functions can return side effects to be performed by the runtime
in the form of an [`Effect`](/api/lustre/effect#effect-type).

### start | javascript

```gleam
pub fn start(
  app: App(flags, model, msg),
  selector: String,
  flags: flags,
) -> Result(fn(msg) -> Nil, Error)
```

Start an application by providing a CSS selector to find the element to mount the
application onto and any flags to pass to the application on first init. This
function returns a `Result` and may fail for a number of reasons. Check out the
[`Error`](#error-type) type for more information.

### destroy | javascript

```gleam
pub fn destroy(app: App(flags, model, msg)) -> Result(Nil, Error)
```

Tear down a running application and remove it from the DOM. This can fail if the
application has not yet been started.

## Components

Components take the same Model-View-Update building blocks used to create Lustre
applications and allow them to be used as reusable stateful components. This is
slightly different to how components are used in other frameworks like React
where "component" refers more generally to any reusable piece of UI.

In Lustre, functions that return an `Element` are known as "view functions" and
components are more specific abstractions that encapsulate state and behaviour
you might not want to deal with in your top-level application.

Resist the urge to reach for components too early. The Elm community has managed
to make do without components at all: you can get surprisingly far storing state
in your top level application and passing it down to different view functions.
This comes with the added benefit of it being much easier to reason about your
UI as a whole.

### component | javascript

```gleam
pub fn component(
  name: String,
  init: fn() -> #(model, Effect(msg)),
  update: fn(model, msg) -> #(model, Effect(msg)),
  view: fn(model) -> Element(msg),
  on_attribute_change: Map(String, Decoder(msg)),
) -> Result(Nil, Error)
```

Register a component with the runtime from the familiar Model-View-Update building
blocks. Compared to an application, we have two additional arguments:

- A name for the component. This name must follow the same rules laid out in the
  [custom element spec](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name)
  and should contain a hyphen (`-`) to avoid clashes with built-in HTML elements.
- A map of attribute names to listen for changes to and a decoder for each to
  decode those attributes into messages to send to your component's `update`
  function.

If it feels like the API for registering components is a little more verbose than
you're used to, that's because it is! You can get surprisingly far storing state
in your top level application and passing it down to different view functions
without needing to use components at all. In fact, for communities like Elm this
is the _only_ way to do things.

## Utilities

### is_browser | erlang javascript

```gleam
pub fn is_browser() -> Bool
```

Gleam has conditional compilation depending on whether you are targetting Erlang
or JavaScript, but sometimes you want to be a bit more specific than that and
check if you're running in the browser.

This is a runtime check that will tell you just that. You could use this to create
a view function that renders something simple on the backend but more complex or
interactive on the frontend.

### is_registered | erlang javascript

```gleam
pub fn is_registered(name: String) -> Bool
```

Lustre's components are built directly on the
[custom element spec](https://html.spec.whatwg.org/multipage/custom-elements.html)
which means they share the same global registery as other custom elements. This
function can tell you if the name you want to use is already registered, by another
Lustre component or otherwise.