Yew Tutorial (v0.19) - part 2

Page content

I continued learning Yew after [this post]({{< ref “rust/yew_tutorial_1.md” >}}).

Important warning

As of Feb. 12 2022, I’ve followed/referred the “Next” version of the Yew documentation (not only v0.19) because there are lots of changes.

And I found this statement:

You are currently reading about function components - the recommended way to write components when starting with Yew.

But we have to note that there is a more advanced, but less recommended way to write them - Struct components

In this post, I followed the “Struct components” way, so you can skip this post.

Note: wasm-pack approach was failed because of version - sample code

There was a template repository in the official tutorial.

https://github.com/yewstack/yew-wasm-pack-minimal

As far as I tried as of Feb 2 2022, the minimal template in documentation won’t be built with version 0.19. (0.17 and 0,18 are OK)

git clone git@github.com:yewstack/yew-wasm-pack-minimal.git
cd yew-wasm-pack-minimal
wasm-pack build --target web

Feb. 2022: Error message

error[E0407]: method `change` is not a member of trait `Component`
error[E0412]: cannot find type `ComponentLink` in this scope
error[E0050]: method `create` has 2 parameters but the declaration in trait `create` has 1

Second tutorial example (OK with v0.19)

I followed another example in the official document:

https://yew.rs/docs/getting-started/build-a-sample-app

Follow the tutorial (but slightly customized)

Create a project by cargo new --lib yew-app.

Cargo.toml:

[package]
name = "yew-app"
version = "0.1.0"
edition = "2018"

[dependencies]
yew = "^0.19"
wasm-bindgen = "^0.2"

[lib]
crate-type = ["rlib", "cdylib"]

src/lib.rs:

use yew::prelude::*;
use wasm_bindgen::prelude::*;

enum Msg {
    AddOne,
}

struct Model {
    value: i64,
}   
    
impl Component for Model {
    type Message = Msg;
    type Properties = ();
    
    fn create(_ctx: &Context<Self>) -> Self {
        Self {
            value: 0,
        }
    }

    fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            Msg::AddOne => {
                self.value += 1;
                // the value has changed so we need to
                // re-render for it to appear on the page
                true
            }
        }
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        // This gives us a component's "`Scope`" which allows us to send messages, etc to the component.
        let link = ctx.link();
        html! {
            <div>
                <button onclick={link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
                <p>{ self.value }</p>
            </div>
        }
    }
}

#[wasm_bindgen(start)]
pub fn run_app() {
    yew::start_app::<Model>();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Yew App</title>
  </head>
</html>

Build:

wasm-pack build --target web

Result:

➜ tree -I target
.
├── Cargo.lock
├── Cargo.toml
├── index.html
├── pkg
│   ├── package.json
│   ├── yew_app_bg.wasm
│   ├── yew_app_bg.wasm.d.ts
│   ├── yew_app.d.ts
│   └── yew_app.js
└── src
    └── lib.rs

You can bundle with Trunk:

trunk serve

Trunk generate bundled app in dist directory. And, yes! You can see a click counter on localhost:8080.

Lifecycles

create, update, view are called lifecycle methods.

You must look through this GitHub issue. GJ, and thank you @mc1098!

Cited from the issue, this image represent when the view method will be kicked (when a new viiew would be rendered):

image alt text
Yew lifecycle trigger

And here is a whole lifecycle of a Component:

image alt text
Yew whole life cycle of a component

crateviewrenderedupdate
When a component is created, it receives properties from its parent component and is stored within the Context<Self> thats passed down to the create method.The view method allows you to describe how a component should be rendered to the DOM.The rendered component lifecycle method is called once view has been called and Yew has rendered the results to the DOM, but before the browser refreshes the page.Communication with components happens primarily through messages which are handled by the update lifecycle method. This allows the component to update itself based on what the message was, and determine if it needs to re-render itself.

Associated Types

There are two assciated types for Components, Message and Properties.

The Message type is used to send messages to a component after an event has taken place:

enum Msg {
    AddOne,
}
//--snip--
impl Component for Model {
    type Message = Msg;

Properties represents the information passed to a component from its parent. This type must implement the Properties trait (usually by deriving it) and can specify whether certain properties are required or optional.

Context

All component lifecycle methods take a context object. This object provides a reference to component’s scope, which allows sending messages to a component and the props passed to the component.

Context.link can be used to register callbacks or send messages to the component.

fn view(&self, ctx: &Context<Self>) -> Html {
    // This gives us a component's "`Scope`" which allows us to send messages, etc to the component.
    let link = ctx.link();
    html! {
        <div>
            <button onclick={link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
            <p>{ self.value }</p>
        </div>
    }
}

About lib

https://doc.rust-lang.org/reference/linkage.html

rlib: “Rust library” file will be produced. This is used as an intermediate artifact and can be thought of as a “static Rust library”. These rlib files, unlike staticlib files, are interpreted by the compiler in future linkage.

cdylib: A dynamic system library will be produced. This is used when compiling a dynamic library to be loaded from another language. This output type will create *.so files on Linux, *.dylib files on macOS, and *.dll files on Windows.

If you don’t include these two, wasm-pack returns error as follows:

➜ wasm-pack build --target web
Error: crate-type must be cdylib to compile to wasm32-unknown-unknown. Add the following to your Cargo.toml file:

[lib]
crate-type = ["cdylib", "rlib"]

How final WASM runs in this case

  1. Access to localhost:8080
  2. The bundled index.html import index-***.js and index-***.wasm.
  3. WASM magic 🪄.
  4. body injected.

Get out from tutirial hell?

With the two example, you could get an very high overview how Yew works.

It’s time to read Concepts section of the official document.

Memo: next good hands-on?

https://blog.logrocket.com/rust-webassembly-frontend-web-app-yew/

v0.18, but good example.

Design (CSS)

Design in Yew is still in discussion, but SCSS/tailwind will be mainstream?