---
title: 'Tutorial'
slug: /tutorial
---

## Introduction

In this hands-on tutorial, we will take a look at how we can use Yew to build web applications.
**Yew** is a modern [Rust](https://www.rust-lang.org/) framework for building front-end web apps using [WebAssembly](https://webassembly.org/).
Yew encourages a reusable, maintainable, and well-structured architecture by leveraging Rust's powerful type system.
A large ecosystem of community-created libraries, known in Rust as [crates](https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html),
provide components for commonly-used patterns such as state management.
[Cargo](https://doc.rust-lang.org/cargo/), the package manager for Rust, allows us to take advantage of the
numerous crates available on [crates.io](https://crates.io), such as Yew.

### What we are going to build

Rustconf is an intergalactic gathering of the Rust community that happens annually.
Rustconf 2020 had a plethora of talks that provided a good amount of information.
In this hands-on tutorial, we will be building a web application to help fellow Rustaceans
get an overview of the talks and watch them all from one page.

## Setting up

### Prerequisites

This tutorial assumes you are already familiar with Rust. If you are new to Rust,
the free [Rust Book](https://doc.rust-lang.org/book/ch00-00-introduction.html) offers a great starting point for
beginners and continues to be an excellent resource even for experienced Rust developers.

Ensure the latest version of Rust is installed by running `rustup update` or by
[installing rust](https://www.rust-lang.org/tools/install) if you have not already done so.

After installing Rust, you can use Cargo to install `trunk` by running:

```bash
cargo install trunk
```

We will also need to add the WASM build target by running:

```bash
rustup target add wasm32-unknown-unknown
```

### Setting up the project

First, create a new cargo project:

```bash
cargo new yew-app
cd yew-app
```

To verify the Rust environment is set up properly, run the initial project using the cargo build tool.
After the output about the build process, you should see the expected "Hello, world!" message.

```bash
cargo run
```

## Our first static page

To convert this simple command line application to a basic Yew web application, a few changes are needed.
Add `yew` to the list of dependencies:

```toml title=Cargo.toml {7}
[package]
name = "yew-app"
version = "0.1.0"
edition = "2021"

[dependencies]
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
```

:::info

You only need the feature `csr` if you are building an application.
It will enable the `Renderer` and all client-side rendering-related code.

If you are making a library, do not enable this feature as it will pull in
client-side rendering logic into the server-side rendering bundle.

If you need the Renderer for testing or examples, you should enable it
in the `dev-dependencies` instead.

:::

```rust ,no_run title="src/main.rs"
use yew::prelude::*;

#[component]
fn App() -> Html {
    html! {
        <h1>{ "Hello World" }</h1>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}
```

Now, let's create an `index.html` at the root of the project.

```html title="index.html"
<!doctype html>
<html lang="en">
    <head></head>
    <body></body>
</html>
```

### Start the development server

Run the following command to build and serve the application locally.

```bash
trunk serve --open
```

:::info
Remove option '--open' to not open your default browser `trunk serve`.
:::

Trunk will open your application in your default browser, watch the project directory and helpfully rebuild your
application if you modify any source files.
This will fail if the socket is being used by another application.
By default server will be listening at address '127.0.0.1' and port '8080' => [http://localhost:8080](http://127.0.0.1:8080).
To change it, create the following file and edit as needed:

```toml title="Trunk.toml"
[serve]
# The address to serve on LAN.
address = "127.0.0.1"
# The address to serve on WAN.
# address = "0.0.0.0"
# The port to serve on.
port = 8000
```

If you are curious, you can run `trunk help` and `trunk help <subcommand>` for more details on what is happening.

### Congratulations

You have now successfully set up your Yew development environment and built your first Yew web application.

## Building HTML

Yew makes use of Rust's procedural macros and provides us with a syntax similar to JSX (an extension to JavaScript
which allows you to write HTML-like code inside JavaScript) to create the markup.

### Converting classic HTML

Since we already have a pretty good idea of what our website will look like, we can simply translate our mental draft
into a representation compatible with `html!`. If you are comfortable writing simple HTML, you should have no problem
writing marking inside `html!`. It is important to note that the macro does differ from HTML in a few ways:

1. Expressions must be wrapped in curly braces (`{ }`)
2. You may return any number of root nodes; they are grouped automatically.
3. Elements must be closed properly.

We want to build a layout that looks something like this in raw HTML:

```html
<h1>RustConf Explorer</h1>
<div>
    <h3>Videos to watch</h3>
    <p>John Doe: Building and breaking things</p>
    <p>Jane Smith: The development process</p>
    <p>Matt Miller: The Web 7.0</p>
    <p>Tom Jerry: Mouseless development</p>
</div>
<div>
    <h3>John Doe: Building and breaking things</h3>
    <img
        src="https://placehold.co/640x360.png?text=Video+Player+Placeholder"
        alt="video thumbnail"
    />
</div>
```

Now, let's convert this HTML into `html!`. Type (or copy/paste) the following snippet into the body of `app` function
such that the value of `html!` is returned by the function

```rust {3-19}
#[component]
fn App() -> Html {
-   html! {
-       <h1>{ "Hello World" }</h1>
-   }
+   html! {
+       <h1>{ "RustConf Explorer" }</h1>
+       <div>
+           <h3>{ "Videos to watch" }</h3>
+           <p>{ "John Doe: Building and breaking things" }</p>
+           <p>{ "Jane Smith: The development process" }</p>
+           <p>{ "Matt Miller: The Web 7.0" }</p>
+           <p>{ "Tom Jerry: Mouseless development" }</p>
+       </div>
+       <div>
+           <h3>{ "John Doe: Building and breaking things" }</h3>
+           <img src="https://placehold.co/640x360.png?text=Video+Player+Placeholder" alt="video thumbnail" />
+       </div>
+   }
}
```

Refresh the browser page, and you should see the following output displayed:

![Running WASM application screenshot](/img/tutorial_application_screenshot.png)

### Using Rust language constructs in the markup

A big advantage of writing markup in Rust is that we get all the coolness of Rust in our markup.
Now, instead of hardcoding the list of videos in the HTML, let's define them as a `Vec` of `Video` structs.
We create a simple `struct` (in `main.rs` or any file of our choice) that will hold our data.

```rust
#[derive(Clone, PartialEq)]
struct Video {
    id: usize,
    title: AttrValue,
    speaker: AttrValue,
    url: AttrValue,
}
```

Next, we will create instances of this struct in our `app` function and use those instead of hardcoding the data:

```rust {3-29}
#[component]
fn App() -> Html {
+   let videos = vec![
+       Video {
+           id: 1,
+           title: "Building and breaking things".into(),
+           speaker: "John Doe".into(),
+           url: "https://youtu.be/PsaFVLr8t4E".into(),
+       },
+       Video {
+           id: 2,
+           title: "The development process".into(),
+           speaker: "Jane Smith".into(),
+           url: "https://youtu.be/PsaFVLr8t4E".into(),
+       },
+       Video {
+           id: 3,
+           title: "The Web 7.0".into(),
+           speaker: "Matt Miller".into(),
+           url: "https://youtu.be/PsaFVLr8t4E".into(),
+       },
+       Video {
+           id: 4,
+           title: "Mouseless development".into(),
+           speaker: "Tom Jerry".into(),
+           url: "https://youtu.be/PsaFVLr8t4E".into(),
+       },
+   ];
+
```

To display them, we can use a `for` loop right in the macro in place of the hardcoded HTML:

```rust {5-11}
    html! {
        <h1>{ "RustConf Explorer" }</h1>
        <div>
            <h3>{ "Videos to watch" }</h3>
-           <p>{ "John Doe: Building and breaking things" }</p>
-           <p>{ "Jane Smith: The development process" }</p>
-           <p>{ "Matt Miller: The Web 7.0" }</p>
-           <p>{ "Tom Jerry: Mouseless development" }</p>
+           for video in &videos {
+               <p key={video.id}>{format!("{}: {}", video.speaker, video.title)}</p>
+           }
        </div>
        // ...
    }
```

:::tip
Keys on list items help Yew keep track of which items have changed in the list, resulting in faster re-renders.
[It is always recommended to use keys in lists](/concepts/html/lists.mdx#keyed-lists).
:::

## Components

Components are the building blocks of Yew applications. By combining components, which can be made of other components,
we build our application. By structuring our components for re-usability and keeping them generic, we will be able to use
them in multiple parts of our application without having to duplicate code or logic.

The `app` function we have been using so far is a component, called `App`. It is a "function component".
There are two different types of components in Yew.

1. Struct Components
2. Function Components

In this tutorial, we will be using function components.

Now, let's split up our `App` component into smaller components. We begin by extracting the videos list into
its own component.

```rust
#[derive(Properties, PartialEq)]
struct VideosListProps {
    videos: Vec<Video>,
}

#[component]
fn VideosList(VideosListProps { videos }: &VideosListProps) -> Html {
    html! {
        for video in videos {
            <p key={video.id}>{format!("{}: {}", video.speaker, video.title)}</p>
        }
    }
}
```

Notice the parameters of our `VideosList` function component. A function component takes only one argument which
defines its "props" (short for "properties"). Props are used to pass data down from a parent component to a child component.
In this case, `VideosListProps` is a struct that defines the props.

:::important
The struct used for props must implement `Properties` by deriving it.
:::

Now, we can update our `App` component to make use of `VideosList` component.

```rust {8-11}
#[component]
fn App() -> Html {
    // ...
    html! {
        <h1>{ "RustConf Explorer" }</h1>
        <div>
            <h3>{ "Videos to watch" }</h3>
-           for video in &videos {
-               <p key={video.id}>{format!("{}: {}", video.speaker, video.title)}</p>
-           }
+           <VideosList {videos} />
        </div>
        // ...
    }
}
```

By looking at the browser window, we can verify that the lists are rendered as they should be.
We have moved the rendering logic of lists to its component. This shortens the `App` component’s source code,
making it easier for us to read and understand.

### Making it interactive

The final goal here is to display the selected video. To do that, `VideosList` component needs to "notify" its
parent when a video is selected, which is done via a `Callback`. This concept is called "passing handlers".
We modify its props to take an `on_click` callback:

```rust {4}
#[derive(Properties, PartialEq)]
struct VideosListProps {
    videos: Vec<Video>,
+   on_click: Callback<Video>,
}
```

Then we modify the `VideosList` component to "emit" the selected video to the callback.

```rust {2-18}
#[component]
-fn VideosList(VideosListProps { videos }: &VideosListProps) -> Html {
+fn VideosList(VideosListProps { videos, on_click }: &VideosListProps) -> Html {
+   let on_select = |video: &Video| {
+       let on_click = on_click.clone();
+       let video = video.clone();
+       Callback::from(move |_| {
+           on_click.emit(video.clone())
+       })
+   };
+
    html! {
        for video in videos {
-           <p key={video.id}>{format!("{}: {}", video.speaker, video.title)}</p>
+           <p key={video.id} onclick={on_select(video)}>{format!("{}: {}", video.speaker, video.title)}</p>
        }
    }
}
```

Next, we need to modify the usage of `VideosList` to pass that callback. But before doing that, we should create
a new component, `VideoDetails`, that is displayed when a video is clicked.

```rust
#[derive(Properties, PartialEq)]
struct VideosDetailsProps {
    video: Video,
}

#[component]
fn VideoDetails(VideosDetailsProps { video }: &VideosDetailsProps) -> Html {
    html! {
        <div>
            <h3>{ &*video.title }</h3>
            <img src="https://placehold.co/640x360.png?text=Video+Player+Placeholder" alt="video thumbnail" />
        </div>
    }
}
```

Now, modify the `App` component to display `VideoDetails` component whenever a video is selected.

```rust {3-11,18-19,21-28}
        },
    ];
+
+   let selected_video = use_state(|| None);
+
+   let on_video_select = {
+       let selected_video = selected_video.clone();
+       Callback::from(move |video: Video| {
+           selected_video.set(Some(video))
+       })
+   };

    html! {
        <h1>{ "RustConf Explorer" }</h1>
        <div>
            <h3>{ "Videos to watch" }</h3>
-           <VideosList {videos} />
+           <VideosList {videos} on_click={on_video_select} />
        </div>
+       if let Some(video) = &*selected_video {
+           <VideoDetails video={video.clone()} />
+       }
-       <div>
-           <h3>{ "John Doe: Building and breaking things" }</h3>
-           <img src="https://placehold.co/640x360.png?text=Video+Player+Placeholder" alt="video thumbnail" />
-       </div>
    }
}
```

### Handling state

Remember the `use_state` used earlier? That is a special function, called a "hook". Hooks are used to "hook" into
the lifecycle of a function component and perform actions. You can learn more about this hook, and others
[here](concepts/function-components/hooks/introduction.mdx#pre-defined-hooks).

:::note
Struct components act differently. See [the documentation](advanced-topics/struct-components/introduction.mdx) to learn about those.
:::

## Fetching data (using external REST API)

In a real-world application, data will usually come from an API instead of being hardcoded. Let's fetch our
videos list from an external source. For this we will need to add the following crates:

- [`gloo-net`](https://crates.io/crates/gloo-net)
  For making the fetch call.
- [`serde`](https://serde.rs) with derive features
  For de-serializing the JSON response

Let's update the dependencies in `Cargo.toml` file:

```toml title="Cargo.toml" {2-5}
[dependencies]
-yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
+yew = { git = "https://github.com/yewstack/yew/", features = ["csr", "serde"] }
+gloo-net = "0.7"
+serde = { version = "1.0", features = ["derive"] }
```

Yew's `serde` feature enables integration with the `serde` crate, the important point for us is that
it adds a `serde::Deserialize` impl to `AttrValue`.

:::note
When choosing dependencies make sure they are `wasm32` compatible!
Otherwise you won't be able to run your application.
:::

Update the imports:

```rust {2}
use yew::prelude::*;
+use serde::Deserialize;
```

Update the `Video` struct to derive the `Deserialize` trait:

```rust {1-2}
-#[derive(Clone, PartialEq)]
+#[derive(Clone, PartialEq, Deserialize)]
struct Video {
    id: usize,
    title: AttrValue,
    speaker: AttrValue,
    url: AttrValue,
}
```

Now we need to update our `App` component to fetch data. The modern yew way to do this is with [`use_future`](https://docs.rs/yew/latest/yew/suspense/fn.use_future.html) and [`<Suspense>`](https://yew.rs/docs/concepts/suspense).

Alternatively, you can use [`yew::platform::spawn_local`](https://docs.rs/yew/latest/yew/platform/fn.spawn_local.html)
if hooks are unavailable, such as within struct components or standard functions.

`use_future` suspends the component until the async operation completes, and `<Suspense>` shows a
fallback UI (e.g. a loading indicator) in the meantime.

Update the imports:

```rust {3-4}
use yew::prelude::*;
use serde::Deserialize;
+use gloo_net::http::Request;
+use yew::suspense::use_future;
```

We split the data fetching logic into a child component (`VideosFetcher`) that returns `HtmlResult`,
while the parent `App` wraps it in `<Suspense>`:

```rust {2-6,8-39}
use yew::suspense::use_future;
+#[derive(Properties, PartialEq)]
+struct VideosFetchProps {
+    on_click: Callback<Video>,
+    selected_video: Option<Video>,
+}

+#[component]
+fn VideosFetcher(
+    VideosFetchProps {
+        on_click,
+        selected_video,
+    }: &VideosFetchProps,
+) -> HtmlResult {
+    let videos = use_future(|| async {
+        Request::get("https://yew.rs/tutorial/data.json")
+            .send()
+            .await?
+            .json::<Vec<Video>>()
+            .await
+    })?;
+
+    match &*videos {
+        Ok(videos) => Ok(html! {
+            <>
+                <div>
+                    <h3>{ "Videos to watch" }</h3>
+                    <VideosList videos={videos.clone()} on_click={on_click.clone()} />
+                </div>
+                if let Some(video) = selected_video {
+                    <VideoDetails video={video.clone()} />
+                }
+            </>
+        }),
+        Err(err) => Ok(html! {
+            <p>{format!("Error fetching videos: {err}")}</p>
+        }),
+    }
+}
```

Now we will use the new component inside the App component.

```rust {3-28,33-47}
#[component]
fn App() -> Html {
-   let videos = vec![
-       Video {
-           id: 1,
-           title: "Building and breaking things".into(),
-           speaker: "John Doe".into(),
-           url: "https://youtu.be/PsaFVLr8t4E".into(),
-       },
-       Video {
-           id: 2,
-           title: "The development process".into(),
-           speaker: "Jane Smith".into(),
-           url: "https://youtu.be/PsaFVLr8t4E".into(),
-       },
-       Video {
-           id: 3,
-           title: "The Web 7.0".into(),
-           speaker: "Matt Miller".into(),
-           url: "https://youtu.be/PsaFVLr8t4E".into(),
-       },
-       Video {
-           id: 4,
-           title: "Mouseless development".into(),
-           speaker: "Tom Jerry".into(),
-           url: "https://youtu.be/PsaFVLr8t4E".into(),
-       },
-   ];

    // ...

    html! {
-       <h1>{ "RustConf Explorer" }</h1>
-       <div>
-           <h3>{ "Videos to watch" }</h3>
-           <VideosList {videos} on_click={on_video_select} />
-       </div>
-       if let Some(video) = &*selected_video {
-           <VideoDetails video={video.clone()} />
-       }
+       <h1>{ "RustConf Explorer" }</h1>
+       <Suspense fallback={html! {<p> {"Loading..."} </p>}} >
+           <VideosFetcher
+                   on_click={on_video_select}
+                   selected_video={(*selected_video).clone()}
+               />
+       </Suspense>
    }
}
```

:::note
We use `?` on the `use_future` call to propagate the suspension. The component returns `HtmlResult`
instead of `Html` when using suspense hooks. The parent wraps it in `<Suspense fallback=...>` to show
a loading indicator while the data is being fetched.
:::

Now, look at the browser to see everything working as expected... which would have been the case if it were not for CORS.
To fix that, we need a proxy server. Luckily trunk provides that.

Update the following line:

```rust {1-2}
-       Request::get("https://yew.rs/tutorial/data.json")
+       Request::get("/tutorial/data.json")
```

Now, rerun the server with the following command:

```bash
trunk serve --proxy-backend=https://yew.rs/tutorial
```

Refresh the tab and everything should work as expected.

The loading text may not be visible because the data loads very quickly. If you'd like to see it, you can add an
artificial delay inside the `use_future` hook using `TimeoutFuture`. First, add `gloo-timers` to your `Cargo.toml`:

```toml
gloo-timers = { version = "0.3", features = ["futures"] }
```

Then add the delay inside the hook:

```
let videos = use_future(|| async {
+   gloo_timers::future::TimeoutFuture::new(3_000).await; // 3 second delay
    Request::get("/tutorial/data.json")
        .send()
        .await?
        .json::<Vec<Video>>()
        .await
})?;
```

## Wrapping up

Congratulations! You’ve created a web application that fetches data from an external API and displays a list of videos.

## What's next

This application is very far from perfect or useful. After going through this tutorial,
you can use it as a jumping-off point to explore more advanced topics.

### Styles

Our apps look very ugly. There is no CSS or any kind of style.
Unfortunately, Yew does not offer a built-in way to style components. See [Trunk's assets](https://trunk-rs.github.io/trunk/guide/assets/index.html)
to learn how to add style sheets.

### More libraries

Our app made use of only a few external dependencies. There are lots of crates out there that can be used.
See [external libraries](/community/external-libs) for more details.

### Learning more about Yew

Read our [official documentation](../getting-started/introduction.mdx). It explains a lot of concepts in much more detail.
To learn more about the Yew API, see our [API docs](https://docs.rs/yew).

<!-- COMBINE CODE BLOCKS -->
