lustre

Lustre is a declarative framework for building Web apps in Gleam.

Types

An App describes a Lustre application: what state it holds and what kind of actions get dispatched to update that state. The only useful thing you can do with an App is pass it to start.

You can construct an App from the two constructors exposed in this module: basic and application. Although you can’t do anything but start them, the constructors are separated in case you want to set up an application but defer starting it until some later point in time.

                                      +--------+
                                      |        |
                                      | update |
                                      |        |
                                      +--------+
                                        ^    |
                                        |    | 
                                 Action |    | #(State, Action)
                                        |    |
                                        |    v
  +------+                    +------------------------+
  |      |  #(State, Action)  |                        |
  | init |------------------->|     Lustre Runtime     |
  |      |                    |                        |
  +------+                    +------------------------+
                                        ^    |
                                        |    |
                                 Action |    | State
                                        |    |
                                        |    v
                                      +--------+
                                      |        |
                                      | render |
                                      |        |
                                      +--------+

Someone please PR the Gleam docs generator to fix the monospace font, thanks! 💖

pub opaque type App(state, action)
pub type Error {
  ElementNotFound
}

Constructors

  • ElementNotFound

Functions

pub fn application(init: #(a, Cmd(b)), update: fn(a, b) ->
    #(a, Cmd(b)), render: fn(a) -> Element(b)) -> App(a, b)

An evolution of a simple app that allows you to return a Cmd from your init and updates. Commands give us a way to perform side effects like sending an HTTP request or running a timer and then dispatch actions back to the runtime to trigger an update.

import lustre
import lustre/cmd
import lustre/element

pub fn main () {
    let init = #(0, tick())
    let update = fn (state, action) {
        case action {
            Tick -> #(state + 1, tick())
        }
    }
    let render = fn (state) {
        element.div([], [
            element.text("Count is: ")
            element.text(state |> int.to_string |> element.text)
        ])
    }

    let app = lustre.simple(init, update, render)
    lustre.start(app, "#root")   
}

fn tick () -> Cmd(Action) {
    cmd.from(fn (dispatch) {
        setInterval(fn () {
            dispatch(Tick)
        }, 1000)
    })
}

external fn set_timeout (f: fn () -> a, delay: Int) -> Nil 
    = "" "window.setTimeout"
pub fn element(element: Element(a)) -> App(Nil, a)

Create a basic lustre app that just renders some element on the page. Note that this doesn’t mean the content is static! With element.stateful you can still create components with local state.

Basic lustre apps don’t have any global application state and so the plumbing is a lot simpler. If you find yourself passing lots of state around, you might want to consider using simple or application instead.

import lustre
import lustre/element

pub fn main () {
    let app = lustre.element(
        element.h1([], [
            element.text("Hello, world!") 
        ])
    )

    lustre.start(app, "#root")
}
pub fn simple(init: a, update: fn(a, b) -> a, render: fn(a) ->
    Element(b)) -> App(a, b)

If you start off with a simple [element](#element) app, you may find yourself leaning on stateful elements to manage state used throughout your app. If that’s the case or if you know you need some global state from the get-go, you might want to construct a simple app instead.

This is one app constructor that allows your HTML elements to dispatch actions to update your program state.

import gleam/int
import lustre
import lustre/element
import lustre/event.{ dispatch }

type Action {
    Incr
    Decr
}

pub fn main () {
    let init = 0
    let update = fn (state, action) {
        case action {
            Incr -> state + 1
            Decr -> state - 1
        }
    }
    let render = fn (state) {
        element.div([], [
            element.button([ event.on_click(dispatch(Decr)) ], [
                element.text("-")
            ]),
            element.text(state |> int.to_string |> element.text),
            element.button([ event.on_click(dispatch(Incr)) ], [
                element.text("+")
            ])
        ])
    }

    let app = lustre.simple(init, update, render)
    lustre.start(app, "#root")
}
pub fn start(app: App(a, b), selector: String) -> Result(
  fn(b) -> Nil,
  Error,
)

Once you have created a app with either basic or application, you need to actually start it! This function will mount your app to the DOM node that matches the query selector you provide.

If everything mounted OK, we’ll get back a dispatch function that you can call to send actions to your app and trigger an update.

import lustre

pub fn main () {
    let app = lustre.appliation(init, update, render)
    assert Ok(dispatch) = lustre.start(app, "#root")

    dispatch(Incr)
    dispatch(Incr)
    dispatch(Incr)
}