Dark mode

Msg

Counter example part:

// (Remove the line below once any of your `Msg` variants doesn't implement `Copy`.)
#[derive(Copy, Clone)]
// `Msg` describes the different events you can modify state with.
enum Msg {
    Increment,
}
Example from a production app (this website)
pub enum Msg {
    UrlChanged(subs::UrlChanged),
    ScrollToTop,
    ToggleGuideList,
    HideGuideList,
    ToggleMenu,
    HideMenu,
    SearchQueryChanged(String),
    ToggleMode,
    SwitchVersion(SeedVersion),
}

  • Msgs are sent when something interesting has happened (e.g. the user has clicked a button) and your app should respond in some way (e.g. a value in your Model should be increased).

  • You can also send Msg by calling orders.send_msg(Msg::MyMessage)

  • Msg has similar limitations like Model - it can be almost anything, however the most Msgs are enums in real-world apps. And Msg has to be static (it basically means that you can't send the most messages that contain references).

(We'll talk about sending and handling messages in next chapters.)

How to write a good Msg

  • All Msg variants should be as simple as possible and hold only data or ideally nothing.

    • Example: enum Msg { MenuItemClicked(MenuItemId), Save }
  • If you can choose the type for your ids (e.g. MenuItemId), pick Uuid rather than String or u32. Uuid implements Copy and allows you to create entities in your frontend app before they are sent to your server.

  • Don't make your life unnecessarily hard.

    • Don't make your Msg generic.
    • Don't implement any Msg methods.
  • Try to be as expressive as possible - reduce the number of simple types (bool, String, u32, Option, etc.) to a minimum. Even type aliases are a huge improvement for readability.

  • There are basically two types of messages. Try to follow the naming conventions:

    • Commands - e.g. ScrollToTop, ToggleMenu, RemoveItem(ItemId), etc.
    • Events - e.g. ButtonClicked, UrlChanged(subs::UrlChanged), TextUpdated(String), etc.

Attribute derive

#[derive(Copy, Clone)]

We already know about the allow attribute from the previous chapters.

The most used attribute, however, is likely derive. I recommend reading the official documentation, particularly these parts:

Notes from the front line:

  • Derive Copy and Clone for all items, where possible. It will make your life easier, and both Clippy and your users will appreciate it.

    • Some derive values are order-sensitive because they are sequentially applied to the code below them. That's why the alphabetical order in derive(..) is not important and by convention Copy should be always before Clone for better code scannability.
  • There are edge-cases where you derive Clone without any compilation issues, but you have still problems with calling my_item.clone(). In the most cases, you can resolve this by implementing Clone manually. (Related Rust issue)

  • Derive Debug at for all public items, if possible. Users will be able to fix the most of the bugs in their apps, and you'll receive more meaningful bug reports. Seed has a log!(item_a, item_b); macro that you can use instead of println! to log things that implement Debug directly to the browser console.

  • Only derive Default when you are sure it's really useful. If you decide to implement Default manually, make it as simple as possible.

  • Eq and PartialEq are often useful for simple enums. They allow you to use the == operator, as opposed to things like pattern matching and the matches! macro, which are less readable. Example:

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum Visibility {
    Visible,
    Hidden,
}
...
   IF!(model.menu_visibility == Hidden => C.hidden)
   // VS
   IF!(matches!(model.menu_visibility, Hidden) => C.hidden)
   // or
   if let Hidden = model.menu_visibility { Some(C.hidden) } else { None }

(You'll learn more about Seed macro IF! in next chapters; C. is a typed CSS class container used in seed-quickstart-webpack.)