README

#Cmd

The Cmd[Msg] type represent a task that has not yet been executed. It's a side effect managed by the runtime. The only way to run the command is return it from the update function:

fn update(msg : Msg, model : Model) -> (Cmd[Msg], Model) {
match msg {
Msg::Click(id) => {
// create a command to tell rabbit-tea scroll to the element with the given id
let cmd = @nav.scroll_to(id)
let updated_model = {...}
(cmd, updated_model)
}
}
}

The scroll_to function creates a cmd value that is returned with the updated_model. This means the scrolling action does not occur immediately. Instead, it will scroll to the element with the specified id after the updated_model has been rendered.

#Custom Command

You can encapsulate a command to interoperate Rabbit-Tea with the external JavaScript world.

The Cmd type acts as a wrapper for a callback function that will be executed by the runtime at a later point:

type Cmd[M] (Events[M]) -> Unit

It accepts the Events[M] type as a parameter, which is a collection of events that can be triggered by your command. The most common usage is to trigger another update with a message:

fn delay[M](msg : M, ms : Int) -> Cmd[M] {
Cmd(fn(events){
set_timeout(fn(){ events.trigger_update(msg) }, ms)
})
}

extern "js" fn set_timeout(f : () -> Unit, ms : Int) = "(f,ms) => setTimeout(f, ms)"

The @http package is also implemented in a similar manner.

For a complete example, refer to src/example/custom_command.

#Design Considerations

Why do we wrap tasks in Cmd instead of running them immediately? Here are some reasons:

  1. Tasks Need to Run After the New Model Is Rendered

    • As shown in the example above, the scroll_to function must execute after the new model is rendered. If it runs immediately, the scroll action may not work as expected.

    • For instance, you might need to update the UI to a loading state before fetching data. If the HTTP task is executed immediately, the app could lose responsiveness.

    You might ask: "Why not make the update function asynchronous so tasks can run asynchronously, allowing the UI to update between those tasks?" Here's another reason:

  2. Encourages users follows the Single Source of Truth Principle

    The single source of truth principle ensures that the new model and view are computed based on a single, consistent model.

    If the update function were asynchronous, updates could overlap, leading to situations where one update process occurs before another is completed. This could result in inconsistent states or unexpected behavior.

    +---> update(msg1, old_model) ---> new_model1 | old_model--+ | +---> update(msg2, old_model) ---> new_model2

    Now you have two models, new_model1 and new_model2. Which model should be used in the view?

    In some other UI frameworks, this issue could occur. In rabbit-tea, it can be avoided by using the Cmd pattern. If you doesn't use asynchronous functions in update, e.g. @cmd.attempt and @cmd.perform, you will never met this problem.

#
Cmd

#alias(Command)
pub(all) type Cmd[M] (Events[M]) -> Unit

The command type, represents a task that can be executed.

You can define your own command to interoperate Rabbit-Tea with the outside JS world.

Before implementing your own command, check the existing commands in the nav and http packages.

Example

fn delay[M](msg : M, ms : Int) -> Cmd[M] {
Cmd(events => {
set_timeout(() => { events.trigger_update(msg) }, ms)
})
}

extern "js" fn set_timeout(f : () -> Unit, ms : Int) = "(f,ms) => setTimeout(f, ms)"

#
Cmd::inner

#deprecated("Use `struct T(A)` to declare a newtype and use `.0` access the underlying type instead.")
fn[M] Cmd::inner(self : Cmd[M]) -> ((Events[M]) -> Unit)
Convert newtype to its underlying type, automatically derived.

#
Cmd::map

fn[A, B] Cmd::map(self : Cmd[A], f : (A) -> B) -> Cmd[B]

Map the messages in the command to another type.

#
Events

type Events[M]

Store the events that can be triggered by the command.

#
Events::new

fn[M] Events::new(on_url_changed : (
Url
) -> Unit, on_url_request : (
UrlRequest
) -> Unit, on_update : (M) -> Unit) -> Events[M]

Used by the runtime.

#
Events::trigger_update

fn[M] Events::trigger_update(self : Events[M], msg : M) -> Unit

Trigger the update function with message msg.

#
Events::trigger_url_changed

fn[M] Events::trigger_url_changed(self : Events[M], url :
Url
) -> Unit

Trigger the update function with url_changed message config by the user.

#
Events::trigger_url_request

fn[M] Events::trigger_url_request(self : Events[M], url :
UrlRequest
) -> Unit

Trigger the update function with url_request message config by the user.

#
attempt

fn[A, E : Error, M] attempt(msg : (Result[A, E]) -> M, f : async () -> A raise E) -> Cmd[M]

Create a command that runs an async function and handles errors.

This is similar to perform, but it converts the returned value or thrown error into a Result.

#
batch

fn[M] batch(xs : Array[Cmd[M]]) -> Cmd[M]

Create a command that runs multiple commands.

#
none

fn[M] none() -> Cmd[M]

Create a command that does nothing.

#
perform

fn[A, M] perform(msg : (A) -> M, f : async () -> A noraise) -> Cmd[M]

Create a command that runs an async function.

The async function f will be called, and the result will be wrapped in a message msg, then trigger another update with this message.

#
task

fn[M] task(message : M) -> Cmd[M]

Create a command that trigger another update for the given message.

Source Files

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io