Props and Nested Components with Yew
Tags: rust, frontend
Published: 2020-11-29 18:42:56

---
title: Props and Nested Components with Yew
published: true
tags: rust, frontend
series: Yew
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/otcad4jqmtos8khn4g20.jpg
---

Until now, we learned how to create components and how to implement basic interactions for them, but to wield the full power of a component system, we need to pass props down from parent to child components. Nesting of components into each other is also crucial.

Since no Yew dev came to question my methods, I'll see this as a sign that I'm not teaching you, and, in turn, myself crap. :D

[TL;DR Again, the code can be found on GitHub](https://github.com/fllstck/yew-props)

# Setup

I won't go into installing Rust, wasm_bindgen, or setting up a Yew project. [All that can be found in the first article of this series](https://dev.to/fllstck/intro-to-yew-a-rust-frontend-framework-20hb).

# Creating the Main Component

Let's start with the root component of our app. Write the following code into your `src/lib.rs` file:

```rust
#![recursion_limit = "1000"]

mod list;

use list::ListGroup;
use list::ListGroupItem;

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

struct Model {}

impl Component for Model {
    type Message = ();
    type Properties = ();

    fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
        Self {}
    }

    fn update(&mut self, _msg: Self::Message) -> ShouldRender {
        false
    }

    fn change(&mut self, _props: Self::Properties) -> ShouldRender {
        false
    }

    fn view(&self) -> Html {
        html! {
            <ListGroup>
                <ListGroupItem>{"First"}</ListGroupItem>
                <ListGroupItem active=true>{"Second"}</ListGroupItem>
                <ListGroupItem>{"Third"}</ListGroupItem>
            </ListGroup>
        }
    }
}

#[wasm_bindgen(start)]
pub fn run_app() {
    App::<Model>::new().mount_to_body();
}
```

If you read the previous article, most of that code should seem familiar, and if you have used any of the major frontend frameworks lately, even the new code in the `view` method doesn't seem any special. Only the text-nodes may seem a bit out of place with their extra syntax, but somehow Rust seems to have "opinions" about strings, haha.

Anyway, at the top, we declare a new module `list`. 

The `mod list` statement tells Rust there is a module in a file called `list.rs`, which is in the same directory as the current file.

The `use` statement allows us to *use* the public values inside that module as defined in the current file.

# Creating the List Component

Next, we need to implement the components we used in the root component. For this, let's create a `src/list.rs` file and insert this code:

```rust
use yew::prelude::*;

pub struct ListGroup {
    props: ListGroupProps,
}

#[derive(Properties, Clone)]
pub struct ListGroupProps {
    pub children: Children,
}

impl Component for ListGroup {
    type Message = ();
    type Properties = ListGroupProps;

    fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
        Self { props }
    }

    fn update(&mut self, _msg: Self::Message) -> ShouldRender {
        false
    }

    fn change(&mut self, props: Self::Properties) -> ShouldRender {
        self.props = props;
        true
    }

    fn view(&self) -> Html {
        html! {
            <ul class="list-group">{ self.props.children.clone() }</ul>
        }
    }
}
```

So what's going on here?

First, the struct that holds our components state has a `props` field. This field can be used to access the props later and also to compare new props to the old ones when we want to safe performance on re-renders.

Then the struct that actually holds our props. In this case, we only accept `children` as props, nothing else. The attribute above the struct definition here gives the struct some extra features that wouldn't be available in a plain struct. These features are required that Yew recognizes the struct as a props struct.

Next comes the trait implementation. We need to set the `Properties` type to the props struct we defined and then implement a few methods.

The first interesting method is `create`, where we save the received `props` into our state.

Then the `change` method, where we do the same. This time we could check if the props actually changed and return `false` if they didn't. Doing so would prevent a re-render.

Finally, the `view` method. Here we get our state struct as `self`. Since state struct holds our `props` and, in turn, our `props` hold `children`, we can call `clone()` on them to get a copy of our `children` that will be rendered into an unsorted list. 

# Creating the Item Component

Now we need the actual list items our list should hold. Since the list and the item component belong together, let's put them in the same file. Add this code at the bottom of the `src/list.rs` file:

```rust
pub struct ListGroupItem {
    props: ListGroupItemProps,
}

#[derive(Properties, Clone)]
pub struct ListGroupItemProps {
    pub children: Children,
    #[prop_or(false)]
    pub active: bool,
}

impl Component for ListGroupItem {
    type Message = ();
    type Properties = ListGroupItemProps;

    fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
        Self { props }
    }

    fn update(&mut self, _msg: Self::Message) -> ShouldRender {
        false
    }

    fn change(&mut self, props: Self::Properties) -> ShouldRender {
        self.props = props;
        true
    }

    fn view(&self) -> Html {
        let mut classes = vec!["list-group-item"];

        if self.props.active {
            classes.push("active")
        }

        html! {
            <li class=classes>{ self.props.children.clone() }</li>
        }
    }
}
```

The item component isn't much different from the list component. It renders to a list item HTML element and passes its children along.

The only extra is an `active` prop. This allows marking the component with different styling.

All props have to be marked public because the users of our components need to know what type every prop has.

With the `#[prop_or(false)]` attribute we can give every prop a default value. If you forget to do this and don't pass that value, you will get bizarre error messages with nothing to do with the missing value. So "YAY" for type-systems :D

In the `view` method, we use the `active` prop to conditionally concatenate a vector that will serve as our CSS class definition.

Yew accepts strings, vectors, and tuples as values for `class`. 

# Building & Testing the App

To build the app, run this command:

    $ wasm-pack build --target web --out-name wasm --out-dir ./static

If you followed [the instructions of the first tutorial](https://dev.to/fllstck/intro-to-yew-a-rust-frontend-framework-20hb), you should now be able to run:

    $ miniserve ./static

And [check out your app in the browser!](http://127.0.0.1:8080)

# Typing Children

You're probably thinking, why can't we type the `children` props more specific?

Well, we can!

Instead of using the catch-all type `Children` we use the generic type `ChildrenWithProps<T>` where `T` is the item type we use.

So we have to refactor the props type of our list as follows:

```rust
#[derive(Properties, Clone)]
pub struct ListGroupProps {
    pub children: ChildrenWithProps<ListGroupItem>,
}
```

This way, our list component only accepts a list of our item types as children and the compiler will warn the users if they used the wrong type.

# Bonus: Binary Size Optimization

The WASM binary created is rather big; on my machine, it was ~130KB. So, I thought, let's look into some improvements here.

If you add a release profile to the bottom of the `Cargo.toml` file, things should shrink a bit.

```toml
[profile.release]
panic = 'abort' 
codegen-units = 1
opt-level = 'z' 
lto = true
```

You can also swap out the memory allocator. There is one created explicitly for the WASM compile target and is smaller than the default one. This allocator is called `wee_alloc` and can be installed by adding it as a dependency to your `Cargo.toml`.

```toml
[dependencies]
yew = "0.17"
wasm-bindgen = "0.2.67"
wee_alloc = "0.4.2"
```

And activating it at the top of your `src/lib.rs` file.

```rust
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
```

After these changes, I got the build down to ~100KB, which is a reduction of about 25%, not great, not terrible.

# Conclusion

Rust still feels rather chatty, but overall at least Yew seems very familiar for a React dev like me.

Sometimes, the compiler's errors lead you in a completely false direction, but that's not often the case.