## DEV Community

Ben Lovy

Posted on • Updated on

# Let's Build a Rust Frontend with Yew - Part 2

## PART 2

In the first part, we set up our development environment and ensured we can compile and run our webapp. This part starts assuming your project folder mirrors this one. Please start with Part 1 if you have not already done so - or you can skip this one and go right to Part 3 but you'll likely need to come back through here anyway.

Now we can start modelling the logic. We'll start by defining the cave. The traditional game is played in a cave where each room is a vertex of a regular dodecahedron:

From each room we are connected to exactly three other rooms.

To model this we'll simply use a function to map room IDs to available exits. This will allow us to traverse around the cave. Place the following in `lib.rs`, above your `Model` declaration:

``````fn room_exits(id: u8) -> Option<[u8; 3]> {
match id {
1 => Some([2, 5, 8]),
2 => Some([1, 3, 10]),
3 => Some([2, 4, 12]),
4 => Some([3, 5, 14]),
5 => Some([1, 4, 6]),
6 => Some([5, 7, 15]),
7 => Some([6, 8, 17]),
8 => Some([1, 7, 11]),
9 => Some([10, 12, 19]),
10 => Some([2, 9, 11]),
11 => Some([8, 10, 20]),
12 => Some([3, 9, 13]),
13 => Some([12, 14, 18]),
14 => Some([4, 13, 15]),
15 => Some([6, 14, 16]),
16 => Some([15, 17, 18]),
17 => Some([7, 16, 20]),
18 => Some([13, 16, 19]),
19 => Some([9, 18, 20]),
20 => Some([11, 17, 19]),
_ => None
}
}
``````

Now let's store the player's current location in the `Model`:

``````pub struct Model {
arrows: u8,
current_room: u8,
}
``````

Don't forget to add it to our initial model too:

``````  fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
Model {
arrows: 5,
current_room: 1,
}
}
``````

Now we can start adding to our UI. We'll need a new component that will be responsible for rendering the controls. I like keeping all of these in a folder:

``````\$ mkdir src/components
\$ touch src/components/controls.rs
``````

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

pub struct Controls {
title: String,
exits: [u8; 3],
}

pub enum Msg {}

#[derive(PartialEq, Clone)]
pub struct Props {
pub exits: [u8; 3],
}

impl Default for Props {
fn default() -> Self {
Self { exits: [0, 0, 0] }
}
}

impl Component for Controls {
type Message = Msg;
type Properties = Props;

fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
Controls {
title: "Controls".into(),
exits: props.exits,
}
}

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

impl Renderable<Controls> for Controls {
fn view(&self) -> Html<Self> {
html! {
<div class=("container", "container-controls"),>
<div class="title",>{&self.title}</div>
<div class="exits",>{format!("exits: {}, {}, {}", self.exits[0], self.exits[1], self.exits[2])}</div>
</div>
}
}
}
``````

Unlike our top-level component, this one accepts some props - we're going to pass in the exits to the room our player is in. A couple of "gotchas" - take a look at the `html!` macro in the `Renderable` impl block. We're attaching two classes to the top-level `div` - to do so, you need to wrap them up in a tuple like shown. Also, if you're using an attribute in your tag like `<div class="title",>`, you need to include that trailing comma for the macro to work. If you don't, you might end up with a very dense error message - check for these commas before panicking. Rust macros tend to generate pretty opaque error info - one major drawback of the tech at this point in time.

Also of note - we must provide a `Default` impl for our `Props`. I'm just setting it to `[0, 0, 0]`.

Let's position it within our app. First, we have to organize our component module:

``````\$ echo 'pub mod controls;' > src/components/mod.rs
``````

When we add new components, don't forget to add the declaration to this file. Back up in `lib.rs`, add the module directly after your `extern crate` declarations and bring it into scope:

``````mod components;

use self::components::controls::Controls;
``````

Now we can attach it to the app. Down in the `html!` macro let's add the component right below our `<span>` element displaying the arrows. We'll also section off the stats printout and display the current room. Adjust yours to match this:

``````<div class="hunt",>
<div class="body",>
<div class=("container""container-stats"),>
<span class="title",>{"Stats"}</span>
<br/>
<span class="arrows",>{&format!("Arrows: {}", self.arrows)}</span>
<br/>
<span class="current-room",>{&format!("Current Room: {}"self.current_room)}</span>
</div>
<Controls: exits=room_exits(self.current_room).unwrap(),/>
</div>
</div>
``````

Once the rebuild completes, go back to your browser and confirm you see:

Stats
Arrows: 5
Current Room: 1
Controls
exits: 2, 5, 8

Pretty plain, but just what we asked for! Before we get too far into the logic, let's give ourselves something resembling a layout. This is just going to be a skeleton - I'm no CSS guru. Feel free to make this whatever you like, this should be enough to get you started.

Replace `scss/hunt.scss` with the following:

``````.hunt {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;

flex: 0;
font-size: 36px;
font-weight: bold;
text-align: center;
}

.window {
display: flex;
flex-direction: row;
}

.container {
border: solid 1px #000;
display: flex;
flex-direction: column;
overflow: hidden;
margin: 10px;

>.title {
border-bottom: dashed 1px #000;
font-weight: bold;
text-align: center;
}
}
}
``````

Don't forget to run `yarn build:style` to regenerate the compiled CSS.

Let's also go ahead and take the opportunity to just break out the Stats out into their own component. Make a new file `src/components/stats.rs`:

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

pub struct Stats {
title: String,
arrows: u8,
current_room: u8,
}

pub enum Msg {}

#[derive(PartialEq, Clone)]
pub struct Props {
pub arrows: u8,
pub current_room: u8,
}

impl Default for Props {
fn default() -> Self {
Self {
arrows: 0,
current_room: 0,
}
}
}

impl Component for Stats {
type Message = Msg;
type Properties = Props;

fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
Stats {
title: "Stats".into(),
arrows: props.arrows,
current_room: props.current_room,
}
}

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

impl Renderable<Stats> for Stats {
fn view(&self) -> Html<Self> {
html! {
<div class=("container", "container-stats"),>
<span class="title",>{&self.title}</span>
<span class="stat",>{&format!("Arrows: {}", self.arrows)}</span>
<br/>
<span class="stat",>{&format!("Current Room: {}", self.current_room)}</span>
</div>
}
}
}
``````

New we just add it to `src/components/mod.rs`:

``````pub mod controls;
pub mod stats;
``````

and include it in our top level component in `lib.rs`:

``````mod components;

use self::components::{controls::Controls, stats::Stats};

// down to the bottom...

impl Renderable<Model> for Model {
fn view(&self) -> Html<Self> {
html! {
<div class="hunt",>
<div class="window",>
<Stats: arrows=self.arrows, current_room=self.current_room,/>
<Controls: exits=room_exits(self.current_room).unwrap(),/>
</div>
</div>
}
}
}
``````

This gives us a simple flexbox layout that will be easy to extend. Re-run `yarn build:css-once` and reload `localhost:8000` in your browser to make sure the new style got picked up.

Now we're ready to get interactive with it.

Our next order of business is moving around the cave. All of our actual update logic is going to happen in our top-level component. When we first created `lib.rs`, we just made an empty `Msg` type:

``````#[derive(Debug, Clone)]
pub enum Msg {}
``````

To switch `current_room`, we're going to send a `Msg` containing the target room. Let's add the variant first:

``````#[derive(Debug, Clone)]
pub enum Msg {
SwitchRoom(u8),
}
``````

Now we have to handle that message. Inside the `impl Component for Model` block we currently have a stub for `update()`, returning `true`. Now lets actually use the `Self::Message` parameter it accepts:

``````  fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::SwitchRoom(target) => {
self.current_room = target;
true
}
}
}
``````

Don't forget to remove the underscore from `_msg` in the parameter list!

The great thing about using an `enum` for your messages is that the compiler won't let you miss any when you `match` on them - it must be exhaustive. We also get to easily destructure the variant. This pattern is not unlike what Elm offers. You just need to make sure each match arm returns a boolean - or if you like, you can simply return `true` after the `match` block. Controlling on a per-message basis may allow for more granular performance control - some messages may not require a re-render.

This message is simple - it just switches `current_room`. Next we need to generate these messages. Let's dive back in to `src/components/controls.rs`. We can use `crate::Msg` to refer to the toplevel message our buttons will generate.

We can now create a message that can be passed within this component:

``````pub enum Msg {
ButtonPressed(crate::Msg)
}
``````

We also need to add the callback to our props. Yew has a type ready to go:

``````pub struct Controls {
title: String,
exits: [u8; 3],
onsignal: Option<Callback<crate::Msg>>,
}

#[derive(PartialEq, Clone)]
pub struct Props {
pub exits: [u8; 3],
pub onsignal: Option<Callback<crate::Msg>>,
}

impl Default for Props {
fn default() -> Self {
Self {
exits: [0, 0, 0],
onsignal: None,
}
}
}
``````

Finally, add it to our component initalization:

``````fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
Controls {
title: "Controls".into(),
exits: props.exits,
onsignal: props.onsignal,
}
}
``````

Now we can dynamically create buttons to generate our `crate::Msg`. We already have the room targets coming in to the component - we just need a way to create a different button for each. We can abstract this logic out with a local closure in our `view` function:

``````impl Renderable<Controls> for Controls {
fn view(&self) -> Html<Self> {
let move_button = |target: &u8| {
use crate::Msg::*;
let t = *target;
html! {
<span class="control-button",>
<button onclick=|_| Msg::ButtonPressed(SwitchRoom(t)),>{&format!("Move to {}", target)}</button>
</span>
}
};
html! {
<div class=("container", "container-controls"),>
<div class="title",>{&self.title}</div>
<div class="exits",>{ for self.exits.iter().map(move_button) }</div>
</div>
}
}
}
``````

We then map `move_button` over the exits in our state. Another gotcha - you've got to dereference `target` outside of the `html!` macro: `let t = *target`. If our type wasn't `Copy` like `u8`, we'd need to clone it here.

Now we need to handle the message. Let's fill in our `update`:

``````fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ButtonPressed(msg) => {
if let Some(ref mut callback) = self.onsignal {
callback.emit(msg);
}
}
}
false
}
``````

No need to re-render on the click. We'll handle that later when the state actually changes. We return `false` to make sure we dont waste time on an exra render. Now we just add the prop to `lib.rs`, down in the `view` function:

``````<Controls: exits=room_exits(self.current_room).unwrap(), onsignal=|msg| msg,/>
``````

When the button is clicked the `msg` will fire and our toplevel `update` will handle changing the state. Now we can pass any message we want up as a callback.

There's one final change to make before it all works - we need to tell any component that takes `Props` what to do when those props change. Define these `change` functions in the `impl Component for <...>` blocks of these respective components:

First, `controls.rs`:

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

Then `stats.rs`:

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

Now make sure your `yarn watch:rs` watcher is running and open up `localhost:8000`. You should be able to use the buttons to "explore" the maze.

To keep track of where we've been, let's display a running history for the player. First, we'll add a field to our toplevel state in `lib.rs`:

``````pub struct Model {
arrows: u8,
current_room: u8,
messages: Vec<String>,
}

impl Component for Model {
// ..
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
Model {
arrows: 5,
current_room: 1,
messages: Vec::new(),
}
}
// ..
}
``````

We'll add a new component in a new file `src/components/messages.rs`:

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

pub struct Messages {
title: String,
messages: Vec<String>,
}

pub enum Msg {}

#[derive(PartialEq, Clone)]
pub struct Props {
pub messages: Vec<String>,
}

impl Default for Props {
fn default() -> Self {
Props {
messages: Vec::new(),
}
}
}

impl Component for Messages {
type Message = Msg;
type Properties = Props;

fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
Messages {
title: "Messages".into(),
messages: props.messages,
}
}

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

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

impl Renderable<Messages> for Messages {
fn view(&self) -> Html<Self> {
let view_message = |message: &String| {
html! {
<li>{message}</li>
}
};
html! {
<div class=("container", "container-messages"),>
<div class="title",>{&self.title}</div>
<div class="scroller",>
<ul>{ for self.messages.iter().rev().map(view_message) }</ul>
</div>
</div>
}
}
}
``````

We're showing the messages in reverse - otherwise, this isn't too different from `controls.rs`. Protip - I use a snippet something like this when I'm starting a new component!

Don't forget to add it to `src/components/mod.rs`:

``````pub mod controls;
pub mod messages;
pub mod stats;
``````

And add it to `lib.rs`:

``````use self::components::{controls::Controls, messages::Messages, stats::Stats};

// ..

impl Renderable<Model> for Model {
fn view(&self) -> Html<Self> {
html! {
<div class="hunt",>
<div class="window",>
<Stats: arrows=self.arrows, current_room=self.current_room,/>
<Controls: exits=room_exits(self.current_room).unwrap(), onsignal=|msg| msg,/>
</div>
<Messages: messages=&self.messages,/> // add it down here
</div>
}
}
}
``````

Now let's add a little style in `scss/hunt.scss`. Add the following below the `>.title` block inside the `.container` block:

``````>.scroller {
overflow: auto;
}
``````

and then add right at the end:

``````.hunt {
// ..
.container-messages {
flex: 0 0 192px;
ul {
list-style-type: none;
}
}
}
``````

To pull in the changes, run `yarn build:style`.

Now let's add some messages! We can welcome the player to their likely doom when the game initiates in `lib.rs`:

``````fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
let mut ret = Model {
arrows: 5,
current_room: 1,
messages: Vec::new(),
};
ret.messages.push(
"You've entered a clammy, dark cave, armed with 5 arrows.  You are very cold.".to_string(),
);
ret
}
``````

We'll also log each move:

``````  fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::SwitchRoom(target) => {
self.current_room = target;
self.messages.push(format!("Moved to room {}", target));
true
}
}
}
``````

Nifty! Our cave isn't terribly interesting though. There's some low-hanging fruit, here - there's gotta be a wumpus to hunt!

Join me in Part 3 to make a game out of this treacherous dodecacave.

To compare, here's the completed part 2 code.

Hey great article,
I've been following these posts as a way to learn Rust, I come from C/C++ background, and I got a question:

in lib.rs, where the Control entity is created in the html!{} macro, you pass a closure (|msg| msg)to the property onsignal. so in assence you pass msg, and return msg, is this the case, or what am I missing?
Here is the line:
github.com/deciduously/hunt-the-wu...

Ben Lovy

The `onsignal` prop to `Controls` is a callback - the full type is:

``````pub struct Controls {
title: String,
exits: [u8; 3],
onsignal: Option<Callback<crate::Msg>>,
}
``````

By putting a `crate::Msg` in this type, we're sort of skipping a step of indirection. We're allowing the child component to directly construct the top-level msg type. The component's `Msg` type is `ButtonPressed(SwitchRoom(x))`, so when this message fires in the component it passes up the `SwitchRoom(x)` part up to the parent, which already knows how to handle that message as-is. No further transofrmation is required. Another way to handle this might have been to pass the entire `ButtonPressed` message up, and then in the closure you point out in `lib.rs` you'd have something like:

``````|msg| {
match msg {
ButtonPressed(parent_msg) => parent_msg,
}
}
``````

By using the `crate::Msg` type directly in the component, we can pass the message up as-is. It's like transforming it simply via the identity function.

Does this help clear it up?

Great! Thanks for entertaining me.

I guess my question is: what's the type of the parameter msg in that line, i tried to rewrite the line as |msg:Option>|msg but that threw a compiler error.

and how does the child entity have access to the parent instance?

Thanks for entertaining my doubts :)

Ben Lovy • Edited

The type of `msg` here is simply `crate::Msg`. The trick is that the `onsignal` prop is a closure, and the return value of the closure is a Yew message. This whole closure is the prop that's passed in as a callback. When this closure is invoked Yew will dispatch whatever message it's called with via the `lib.rs` `update` function. The actual call happens in the child:

``````fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ButtonPressed(msg) => {
if let Some(ref mut callback) = self.onsignal {
callback.emit(msg);
}
}
}
false
}
``````

`callback.emit()` is where the parent event is actually fired. The specific message that's getting passed up is pulled out of `ButtonPressed(msg)`. When the `ButtonPressed` event happens as a result of the `onclick` handler, the child component checks to see if there is was a callback provided by the parent - there is, it's `|msg| msg`. Then it emits that callback, filling in `msg` with the message provided by the `ButtonPressed` event, which will be a `crate::Msg::SwitchRoom()` with the right target room. This is why you got a compiler error - right here you're promising to call it with a `crate::Msg`, without the option. You're correct in assuming the source of that error should probably be where you were putting it, not the child component - it's the child that should complain and prompt you to use the correct type. Unfortunately the `html!` macro expansion obfuscates this because it expands prior to actually evaluating and throws first. At least as far as I understand it.

So, the child can't really see the parent, it just invokes the closure that the parent passed in as a prop.

Does that answer your question? I find callbacks a little tricky to talk about clearly, so let me know if I'm just making it more confusing.

Perfectly put :)

Thank for the detailed explanation, now it things make more sense!

For some reason I assumed that the child gets created ( the create method of the child gets called) at the parent html!{} closure, never which caused most of my confusion. While ,at least what I understand from your explanation, the child update function gets called at that stage.

As a follow up question, where would the child starts to exist? and how is the parameters get supplied to the create function?

Thanks again, and sorry for keeping coming back with more questions.

Ben Lovy • Edited

Don't apologize! It's a good check to make sure I know what the heck I'm talking about too.

`create()` is only called once, in `main()`:

``````fn main() {
yew::initialize();
let app: App<Model> = App::new();
app.mount_to_body();  // <- right here
yew::run_loop();
}
``````

`yew::initialize()` is actually just stdweb::initialize(), it sets up the interop config.

The components are all created during `mount_to_body`. By the time we hit `mount_to_body()`, though, the `html!` macros have already expanded into a yew::virtual_dom::VNode tree, because macro expansion happened before the program was evaluated. The VDOM already is built at compile time, and `create()` is called with what you pass in in `html!`.

When we implemented the `Component` trait on this type, we created the `ComponentLink` struct. We don't use it in `create`, but it's still available to the component to use in `update` and `change`. This, through a few nested types, holds the pointer we need:

``````pub struct ComponentLink<COMP: Component> {
scope: Scope<COMP>,
}

pub struct Scope<COMP: Component> {
shared_component: Shared<Option<ComponentRunnable<COMP>>>,
}

struct ComponentRunnable<COMP: Component> {
env: Scope<COMP>,
component: Option<COMP>,
last_frame: Option<VNode<COMP>>,
element: Element,
ancestor: Option<VNode<COMP>>,     // <- right here
occupied: Option<NodeCell>,
init_props: Option<COMP::Properties>,
destroyed: bool,
}
``````

The `update` function is called every time a component receives a `Msg`. When the app is running, each component is sort of like an actor in the Actor model inside the event loop initialized with `yew::run_loop()`.

Caveat to this answer being there's not much documentation or comments for me to source, but the Yew codebase is pretty small and generally readable. I believe I'm at least mostly correct :)

Fair enough,

To be honest, I couldn't yet swallow Rust way of doing things, but our conversation does clear a lot of my doubts.

Thanks a lot, Yew seems very powerful and promising framework. It's worth spending time studying the source code carefully.

Thanks again,
Cheers :)

Ben Lovy

This code, too, isn't necessarily indicative of "normal" Rust, Yew imposes a rigid idiosyncratic model on top of it. Rust is a great, flexible language and i hope you continue to explore!

• `<div class=("container", "container-stats"),>`
• `<span class="current-room",>{&format!("Current Room: {}", self.current_room)}</span>`