Having read the previous blogs in this series, you must have known that:
WASM is not only a way to build and run fast & light-weight frontend apps but also fully functional & complete frontends.
And if you doubt this statement, let's build one ourselves. Let's Gooo🚀💥
🙌 Welcome back to the fourth episode of our journey into building a social media application using Yew, a Rust-based framework for building front-end applications (uses WASM under the hood). In the last episode, we created a basic login page for our social media app. Now, we’ll take a significant step forward by allowing users to add posts and display them using local storage.
What We Will Cover
- Creating Routes for Navigation
- Setting up Post Submission Form
- Displaying Posts from Local Storage
- Using
localStorage
to Persist Posts and Username
If you haven't been following along, feel free to catch up with the previous episodes:
- Episode 1: Introduction to WebAssembly
- Episode 2: Setting up the Yew Environment
- Episode 3: Building a Login Page
Prerequisites
Knowledge of Rust and basic Yew concepts (covered in previous episodes).
Working Yew development environment. All the code in this blog can be used in the main.rs file. Also, make sure you have the required dependencies. This is the Cargo.toml
file for our project:
[package]
name = "your_app_name"
version = "0.1.0"
edition = "2024"
[dependencies]
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
yew-router = { git = "https://github.com/yewstack/yew.git" }
web-sys = "0.3.70"
gloo-storage = "0.3.0"a
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Without further ado, let’s dive in!
Structuring Our Application with Routes
Before we can build the functionality for adding posts, we need to define routes for navigation. We’ll create three pages:
- Home page (which includes a login form)
- Post creation page
- Feed page (where users can view submitted posts)
#[derive(Routable, PartialEq, Clone, Debug)]
enum Route {
#[at("/posts")]
Hello,
#[at("/world")]
World,
#[at("/")]
Home,
#[not_found]
#[at("/404")]
NotFound,
}
In this enum, each variant corresponds to a different route. The Home route will display the login form, Hello is for creating posts, and World will show the list of posts.
We’ll also set up route switching logic that renders the appropriate component for each route:
fn switch(routes: &Route) -> Html {
match routes {
Route::Hello => html! { <Hello /> },
Route::World => html! { <World /> },
Route::Home => html! { <Home /> },
Route::NotFound => html! { <h1>{ "404 - Page Not Found" }</h1> },
}
}
This switch
function ensures that navigating to a different URL correctly renders the associated component.
Adding a Post Submission Form
Next, let’s focus on the Hello component, where users can add new posts. We’ll use Yew’s use_state
hook to manage the post content and localStorage
to store the post and the username.
#[function_component(Hello)]
fn hello() -> Html {
let content = use_state(|| String::new());
let on_content_input = {
let content = content.clone();
Callback::from(move |e: InputEvent| {
let input: web_sys::HtmlInputElement = e.target_unchecked_into();
content.set(input.value());
})
};
let on_submit = {
let content = content.clone();
Callback::from(move |_| {
if let Some(storage) = window().unwrap().local_storage().unwrap() {
if let Ok(Some(username)) = storage.get_item("username") {
let mut posts: Vec<Post> =
if let Ok(Some(posts_str)) = storage.get_item("posts") {
serde_json::from_str(&posts_str).unwrap_or_else(|_| vec![])
} else {
vec![]
};
let new_post = Post {
username: username.clone(),
content: (*content).clone(),
};
posts.push(new_post);
let posts_str = serde_json::to_string(&posts).unwrap();
storage.set_item("posts", &posts_str).unwrap();
web_sys::console::log_1(&"Post added!".into());
}
}
})
};
html! {
<div>
<h1>{ "Hello" }</h1>
<div>
<label for="content">{ "Content: " }</label>
<input id="content" type="text" value={(*content).clone()} oninput={on_content_input} />
</div>
<button onclick={on_submit}>{ "Submit" }</button>
</div>
}
}
The content state stores the current post content.
The on_content_input
function captures user input from the text field.
The on_submit
function handles saving the post to localStorage
. It first retrieves the username from localStorage
(set during login) and then stores the post as a serialized JSON string.
Displaying Posts from Local Storage
Once posts are stored, we can display them on the World page. Here’s how to retrieve and display posts from localStorage
:
#[function_component(World)]
fn world() -> Html {
let posts: Vec<Post> = {
if let Some(storage) = window().unwrap().local_storage().unwrap() {
if let Ok(Some(posts_str)) = storage.get_item("posts") {
serde_json::from_str(&posts_str).unwrap_or_else(|_| vec![])
} else {
vec![]
}
} else {
vec![]
}
};
html! {
<div>
<h1>{ "World" }</h1>
<h2>{ "User Posts" }</h2>
<ul>
{ for posts.iter().map(|post| html! {
<li>
<strong>{ format!("{}: ", post.username) }</strong>
<span>{ &post.content }</span>
</li>
})}
</ul>
</div>
}
}
This component reads posts from localStorage
, deserializes them, and then iterates through the list to display each post with the associated username and content.
Connecting Everything in the Root Component
Finally, let’s tie everything together in our App component. This component uses the Yew Router to render the correct page based on the current route.
#[function_component(App)]
fn app() -> Html {
html! {
<BrowserRouter>
<Switch<Route> render={|routes: Route| switch(&routes)} />
</BrowserRouter>
}
}
The App component will be rendered in the index.html file, which acts as the entry point for our application. Make sure you have a basic HTML setup in index.html like this:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Yew App</title>
</head>
<body>
</body>
</html>
Now just do: trunk serve
and we'll have our app up and running at localhost:8000
:
With this setup, our social media app should be fully functional. You can now navigate between the pages, add posts, and view them in the feed.
So, in this episode, we extended our social media app to support post creation and viewing. By utilizing Yew, we’ve made significant progress toward building a nice little application.
I am not planning to add more functionalities to this application as of now, instead I will be implementing these concepts (WASM, Rust, Yew, etc.) in my other projects and I'll keep sharing my learnings in future blogs.
If you've followed everything till here, you deserve a clap. 😉
Stay tuned and follow me here for more blogs!
👋 Adios, Amigos!!
Top comments (0)