├── static ├── _redirects ├── products │ ├── apple.png │ ├── banana.png │ ├── lemon.png │ ├── mango.png │ ├── avocado.png │ ├── cherries.png │ ├── pineapple.png │ ├── strawberry.png │ ├── tangerine.png │ ├── watermelon.png │ ├── 5.json │ ├── 9.json │ ├── 3.json │ ├── 6.json │ ├── 10.json │ ├── 7.json │ ├── 8.json │ ├── 1.json │ ├── 2.json │ ├── 4.json │ └── products.json ├── README.md ├── index.html └── styles.css ├── banner.png ├── .gitignore ├── src ├── pages │ ├── mod.rs │ ├── product_detail.rs │ └── home.rs ├── components │ ├── mod.rs │ ├── navbar.rs │ ├── atc_button.rs │ └── product_card.rs ├── route.rs ├── lib.rs ├── types.rs ├── api.rs └── app.rs ├── Cargo.toml ├── Makefile.toml └── readme.md /static/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/banner.png -------------------------------------------------------------------------------- /static/products/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/apple.png -------------------------------------------------------------------------------- /static/products/banana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/banana.png -------------------------------------------------------------------------------- /static/products/lemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/lemon.png -------------------------------------------------------------------------------- /static/products/mango.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/mango.png -------------------------------------------------------------------------------- /static/products/avocado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/avocado.png -------------------------------------------------------------------------------- /static/products/cherries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/cherries.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /target 3 | Cargo.lock 4 | static/*.wasm 5 | static/*.d.ts 6 | static/wasm.js 7 | static/package.json -------------------------------------------------------------------------------- /src/pages/mod.rs: -------------------------------------------------------------------------------- 1 | mod home; 2 | mod product_detail; 3 | 4 | pub use home::Home; 5 | pub use product_detail::ProductDetail; 6 | -------------------------------------------------------------------------------- /static/products/pineapple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/pineapple.png -------------------------------------------------------------------------------- /static/products/strawberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/strawberry.png -------------------------------------------------------------------------------- /static/products/tangerine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/tangerine.png -------------------------------------------------------------------------------- /static/products/watermelon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheshbabu/rustmart-yew-example/HEAD/static/products/watermelon.png -------------------------------------------------------------------------------- /static/products/5.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 5, 3 | "name": "Mango", 4 | "description": "", 5 | "image": "/products/mango.png", 6 | "price": 10.0 7 | } 8 | -------------------------------------------------------------------------------- /static/products/9.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 9, 3 | "name": "Avocado", 4 | "description": "", 5 | "image": "/products/avocado.png", 6 | "price": 6.4 7 | } 8 | -------------------------------------------------------------------------------- /static/products/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 3, 3 | "name": "Cherries", 4 | "description": "", 5 | "image": "/products/cherries.png", 6 | "price": 2.93 7 | } 8 | -------------------------------------------------------------------------------- /static/products/6.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 6, 3 | "name": "Tangerine", 4 | "description": "", 5 | "image": "/products/tangerine.png", 6 | "price": 4.79 7 | } 8 | -------------------------------------------------------------------------------- /static/products/10.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 10, 3 | "name": "Pineapple", 4 | "description": "", 5 | "image": "/products/pineapple.png", 6 | "price": 15.0 7 | } 8 | -------------------------------------------------------------------------------- /static/products/7.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 7, 3 | "name": "Watermelon", 4 | "description": "", 5 | "image": "/products/watermelon.png", 6 | "price": 2.4 7 | } 8 | -------------------------------------------------------------------------------- /static/products/8.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 8, 3 | "name": "Strawberry", 4 | "description": "", 5 | "image": "/products/strawberry.png", 6 | "price": 5.0 7 | } 8 | -------------------------------------------------------------------------------- /src/components/mod.rs: -------------------------------------------------------------------------------- 1 | mod atc_button; 2 | mod navbar; 3 | mod product_card; 4 | 5 | pub use atc_button::AtcButton; 6 | pub use navbar::Navbar; 7 | pub use product_card::ProductCard; 8 | -------------------------------------------------------------------------------- /static/products/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Apple", 4 | "description": "An apple a day keeps the doctor away", 5 | "image": "/products/apple.png", 6 | "price": 3.65 7 | } 8 | -------------------------------------------------------------------------------- /static/products/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 2, 3 | "name": "Banana", 4 | "description": "An old banana leaf was once young and green", 5 | "image": "/products/banana.png", 6 | "price": 7.99 7 | } 8 | -------------------------------------------------------------------------------- /src/route.rs: -------------------------------------------------------------------------------- 1 | use yew_router::prelude::*; 2 | 3 | #[derive(Switch, Debug, Clone)] 4 | pub enum Route { 5 | #[to = "/product/{id}"] 6 | ProductDetail(i32), 7 | #[to = "/"] 8 | HomePage, 9 | } 10 | -------------------------------------------------------------------------------- /static/products/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 4, 3 | "name": "Lemon", 4 | "description": "Lemon is a rich source of vitamin C and contains numerous phytochemicals, including polyphenols, terpenes, and tannins.", 5 | "image": "/products/lemon.png", 6 | "price": 1.2 7 | } 8 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod app; 3 | mod components; 4 | mod pages; 5 | mod route; 6 | mod types; 7 | 8 | use wasm_bindgen::prelude::*; 9 | use yew::prelude::*; 10 | 11 | #[wasm_bindgen(start)] 12 | pub fn run_app() { 13 | App::::new().mount_to_body(); 14 | } 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustmart" 3 | version = "0.1.0" 4 | authors = ["sheshbabu "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | yew = "0.17" 12 | yew-router = "0.14.0" 13 | wasm-bindgen = "0.2" 14 | anyhow = "1.0.32" 15 | serde = { version = "1.0", features = ["derive"] } 16 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.build] 2 | command = "wasm-pack" 3 | args = ["build", "--dev", "--target", "web", "--out-name", "wasm", "--out-dir", "./static"] 4 | watch = { ignore_pattern = "static/*" } 5 | 6 | [tasks.serve] 7 | command = "simple-http-server" 8 | args = ["-i", "./static/", "-p", "3000", "--nocache", "--try-file", "./static/index.html"] 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Deserialize, Serialize, Clone, Debug)] 4 | pub struct Product { 5 | pub id: i32, 6 | pub name: String, 7 | pub description: String, 8 | pub image: String, 9 | pub price: f64, 10 | } 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct CartProduct { 14 | pub product: Product, 15 | pub quantity: i32, 16 | } 17 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # RustMart Yew Example 2 | 3 |

4 | 5 | Example Single Page Application built using Rust, Wasm and Yew. [Demo](https://rustmart-yew.netlify.app) 6 | 7 | Read the [blog post](http://www.sheshbabu.com/posts/rust-wasm-yew-single-page-application/) for more details regarding setup and code organization. 8 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | # RustMart Yew Example 2 | 3 |

4 | 5 | Example Single Page Application built using Rust, Wasm and Yew. [Demo](https://rustmart-yew.netlify.app) 6 | 7 | Read the [blog post](http://www.sheshbabu.com/posts/rust-wasm-yew-single-page-application/) for more details regarding setup and code organization. 8 | -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Product; 2 | use anyhow::Error; 3 | use yew::callback::Callback; 4 | use yew::format::{Json, Nothing}; 5 | use yew::services::fetch::{FetchService, FetchTask, Request, Response}; 6 | 7 | pub type FetchResponse = Response>>; 8 | type FetchCallback = Callback>; 9 | 10 | pub fn get_products(callback: FetchCallback>) -> FetchTask { 11 | let req = Request::get("/products/products.json") 12 | .body(Nothing) 13 | .unwrap(); 14 | 15 | FetchService::fetch(req, callback).unwrap() 16 | } 17 | 18 | pub fn get_product(id: i32, callback: FetchCallback) -> FetchTask { 19 | let req = Request::get(format!("/products/{}.json", id)) 20 | .body(Nothing) 21 | .unwrap(); 22 | 23 | FetchService::fetch(req, callback).unwrap() 24 | } 25 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RustMart 6 | 7 | 8 | 13 | 14 | 18 | 19 | 20 | 21 |
22 |
23 |
Loading ...
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/navbar.rs: -------------------------------------------------------------------------------- 1 | use crate::types::CartProduct; 2 | use yew::prelude::*; 3 | 4 | pub struct Navbar { 5 | props: Props, 6 | } 7 | 8 | #[derive(Properties, Clone)] 9 | pub struct Props { 10 | pub cart_products: Vec, 11 | } 12 | 13 | impl Component for Navbar { 14 | type Message = (); 15 | type Properties = Props; 16 | 17 | fn create(props: Self::Properties, _link: ComponentLink) -> Self { 18 | Self { props } 19 | } 20 | 21 | fn update(&mut self, _msg: Self::Message) -> ShouldRender { 22 | true 23 | } 24 | 25 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 26 | self.props = props; 27 | true 28 | } 29 | 30 | fn view(&self) -> Html { 31 | let cart_value = self 32 | .props 33 | .cart_products 34 | .iter() 35 | .fold(0.0, |acc, cp| acc + (cp.quantity as f64 * cp.product.price)); 36 | 37 | html! { 38 | 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/atc_button.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Product; 2 | use yew::prelude::*; 3 | 4 | pub struct AtcButton { 5 | props: Props, 6 | link: ComponentLink, 7 | } 8 | 9 | #[derive(Properties, Clone)] 10 | pub struct Props { 11 | pub product: Product, 12 | pub on_add_to_cart: Callback, 13 | } 14 | 15 | pub enum Msg { 16 | AddToCart, 17 | } 18 | 19 | impl Component for AtcButton { 20 | type Message = Msg; 21 | type Properties = Props; 22 | 23 | fn create(props: Self::Properties, link: ComponentLink) -> Self { 24 | Self { props, link } 25 | } 26 | 27 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 28 | match msg { 29 | Msg::AddToCart => self.props.on_add_to_cart.emit(self.props.product.clone()), 30 | } 31 | true 32 | } 33 | 34 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 35 | self.props = props; 36 | true 37 | } 38 | 39 | fn view(&self) -> Html { 40 | let onclick = self.link.callback(|_| Msg::AddToCart); 41 | 42 | html! { 43 | 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/product_card.rs: -------------------------------------------------------------------------------- 1 | use crate::components::AtcButton; 2 | use crate::route::Route; 3 | use crate::types::Product; 4 | use yew::prelude::*; 5 | use yew_router::components::RouterAnchor; 6 | 7 | pub struct ProductCard { 8 | props: Props, 9 | } 10 | 11 | #[derive(Properties, Clone)] 12 | pub struct Props { 13 | pub product: Product, 14 | pub on_add_to_cart: Callback, 15 | } 16 | 17 | impl Component for ProductCard { 18 | type Message = (); 19 | type Properties = Props; 20 | 21 | fn create(props: Self::Properties, _link: ComponentLink) -> Self { 22 | Self { props } 23 | } 24 | 25 | fn update(&mut self, _msg: Self::Message) -> ShouldRender { 26 | true 27 | } 28 | 29 | fn change(&mut self, _props: Self::Properties) -> ShouldRender { 30 | true 31 | } 32 | 33 | fn view(&self) -> Html { 34 | type Anchor = RouterAnchor; 35 | 36 | html! { 37 |
38 | 39 | 40 |
{&self.props.product.name}
41 |
{"$"}{&self.props.product.price}
42 |
43 | 44 |
45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /static/products/products.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Apple", 5 | "description": "An apple a day keeps the doctor away", 6 | "image": "/products/apple.png", 7 | "price": 3.65 8 | }, 9 | { 10 | "id": 2, 11 | "name": "Banana", 12 | "description": "An old banana leaf was once young and green", 13 | "image": "/products/banana.png", 14 | "price": 7.99 15 | }, 16 | { 17 | "id": 3, 18 | "name": "Cherries", 19 | "description": "", 20 | "image": "/products/cherries.png", 21 | "price": 2.93 22 | }, 23 | { 24 | "id": 4, 25 | "name": "Lemon", 26 | "description": "When life gives you lemons, make lemonade", 27 | "image": "/products/lemon.png", 28 | "price": 1.2 29 | }, 30 | { 31 | "id": 5, 32 | "name": "Mango", 33 | "description": "", 34 | "image": "/products/mango.png", 35 | "price": 10.0 36 | }, 37 | { 38 | "id": 6, 39 | "name": "Tangerine", 40 | "description": "", 41 | "image": "/products/tangerine.png", 42 | "price": 4.79 43 | }, 44 | { 45 | "id": 7, 46 | "name": "Watermelon", 47 | "description": "", 48 | "image": "/products/watermelon.png", 49 | "price": 2.4 50 | }, 51 | { 52 | "id": 8, 53 | "name": "Strawberry", 54 | "description": "", 55 | "image": "/products/strawberry.png", 56 | "price": 5.0 57 | }, 58 | { 59 | "id": 9, 60 | "name": "Avocado", 61 | "description": "", 62 | "image": "/products/avocado.png", 63 | "price": 6.4 64 | }, 65 | { 66 | "id": 10, 67 | "name": "Pineapple", 68 | "description": "", 69 | "image": "/products/pineapple.png", 70 | "price": 15.0 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Navbar; 2 | use crate::types::{CartProduct, Product}; 3 | use yew::prelude::*; 4 | use yew_router::prelude::*; 5 | 6 | use crate::pages::{Home, ProductDetail}; 7 | use crate::route::Route; 8 | 9 | struct State { 10 | cart_products: Vec, 11 | } 12 | 13 | pub struct App { 14 | state: State, 15 | link: ComponentLink, 16 | } 17 | 18 | pub enum Msg { 19 | AddToCart(Product), 20 | } 21 | 22 | impl Component for App { 23 | type Message = Msg; 24 | type Properties = (); 25 | 26 | fn create(_: Self::Properties, link: ComponentLink) -> Self { 27 | let cart_products = vec![]; 28 | 29 | Self { 30 | state: State { cart_products }, 31 | link, 32 | } 33 | } 34 | 35 | fn update(&mut self, message: Self::Message) -> ShouldRender { 36 | match message { 37 | Msg::AddToCart(product) => { 38 | let cart_product = self 39 | .state 40 | .cart_products 41 | .iter_mut() 42 | .find(|cp: &&mut CartProduct| cp.product.id == product.id); 43 | 44 | if let Some(cp) = cart_product { 45 | cp.quantity += 1; 46 | } else { 47 | self.state.cart_products.push(CartProduct { 48 | product: product.clone(), 49 | quantity: 1, 50 | }) 51 | } 52 | true 53 | } 54 | } 55 | } 56 | 57 | fn change(&mut self, _: Self::Properties) -> ShouldRender { 58 | false 59 | } 60 | 61 | fn view(&self) -> Html { 62 | let handle_add_to_cart = self 63 | .link 64 | .callback(|product: Product| Msg::AddToCart(product)); 65 | let cart_products = self.state.cart_products.clone(); 66 | 67 | let render = Router::render(move |switch: Route| match switch { 68 | Route::ProductDetail(id) => { 69 | html! {} 70 | } 71 | Route::HomePage => { 72 | html! {} 73 | } 74 | }); 75 | 76 | html! { 77 | <> 78 | 79 | render=render/> 80 | 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /static/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Source Sans Pro", sans-serif; 3 | -webkit-font-smoothing: subpixel-antialiased; 4 | color: #333; 5 | background: #f7f7f7; 6 | padding-bottom: calc(24px + env(safe-area-inset-bottom)); 7 | } 8 | 9 | .navbar { 10 | height: 44px; 11 | background: #dd2c00; 12 | color: white; 13 | box-shadow: 0px 5px 12px 0px rgba(46, 51, 51, 0.18); 14 | position: sticky; 15 | top: 0; 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | } 20 | 21 | .navbar_title { 22 | font-weight: bold; 23 | font-size: 24px; 24 | margin-left: 10px; 25 | } 26 | 27 | .navbar_cart_value { 28 | font-weight: bold; 29 | font-size: 13px; 30 | margin-right: 10px; 31 | background: #b91400; 32 | padding: 5px 10px; 33 | border-radius: 4px; 34 | } 35 | 36 | .product_card_list { 37 | display: grid; 38 | grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); 39 | grid-gap: 10px; 40 | padding: 10px; 41 | } 42 | 43 | .product_card_container { 44 | border: 1px solid #eee; 45 | border-radius: 4px; 46 | padding: 10px; 47 | cursor: pointer; 48 | box-shadow: 0px 1px 10px 0px rgba(46, 51, 51, 0.18); 49 | background: white; 50 | } 51 | 52 | .product_card_container:hover { 53 | border-color: #ddd; 54 | transition: background, border-color 300ms; 55 | } 56 | 57 | .product_card_anchor { 58 | text-decoration: none; 59 | color: #333; 60 | display: flex; 61 | flex-direction: column; 62 | align-items: center; 63 | } 64 | 65 | .product_card_image { 66 | width: 80%; 67 | object-fit: contain; 68 | } 69 | 70 | .product_card_name, 71 | .product_card_price, 72 | .product_atc_button { 73 | margin-top: 10px; 74 | } 75 | 76 | .product_card_name { 77 | font-weight: bold; 78 | } 79 | 80 | .product_atc_button { 81 | width: 100%; 82 | height: 44px; 83 | border-radius: 4px; 84 | border: 0; 85 | cursor: pointer; 86 | background: #dd2c00; 87 | color: white; 88 | transition: background 300ms; 89 | font-weight: bold; 90 | } 91 | 92 | .product_atc_button:hover { 93 | background: #ddd; 94 | background: #c41c00; 95 | } 96 | 97 | .product_detail_container { 98 | padding: 10px; 99 | cursor: pointer; 100 | background: white; 101 | display: flex; 102 | flex-direction: column; 103 | align-items: center; 104 | } 105 | 106 | .product_detail_image { 107 | height: 30vh; 108 | } 109 | 110 | .loading_spinner_container { 111 | height: 100vh; 112 | width: 100vw; 113 | display: flex; 114 | flex-direction: column; 115 | justify-content: center; 116 | align-items: center; 117 | } 118 | 119 | .loading_spinner { 120 | border-radius: 50%; 121 | width: 18px; 122 | height: 18px; 123 | border: 2px solid #333; 124 | border-top-color: #fff; 125 | animation: loading_spinner_animation 1s infinite linear; 126 | } 127 | 128 | @keyframes loading_spinner_animation { 129 | 0% { 130 | transform: rotate(0deg); 131 | } 132 | 100% { 133 | transform: rotate(360deg); 134 | } 135 | } 136 | 137 | .loading_spinner_text { 138 | margin-top: 10px; 139 | } 140 | -------------------------------------------------------------------------------- /src/pages/product_detail.rs: -------------------------------------------------------------------------------- 1 | use crate::api; 2 | use crate::components::AtcButton; 3 | use crate::types::Product; 4 | use anyhow::Error; 5 | use yew::format::Json; 6 | use yew::prelude::*; 7 | use yew::services::fetch::FetchTask; 8 | 9 | struct State { 10 | product: Option, 11 | get_product_error: Option, 12 | get_product_loaded: bool, 13 | } 14 | 15 | pub struct ProductDetail { 16 | props: Props, 17 | state: State, 18 | link: ComponentLink, 19 | task: Option, 20 | } 21 | 22 | #[derive(Properties, Clone)] 23 | pub struct Props { 24 | pub id: i32, 25 | pub on_add_to_cart: Callback, 26 | } 27 | 28 | pub enum Msg { 29 | GetProduct, 30 | GetProductSuccess(Product), 31 | GetProductError(Error), 32 | } 33 | 34 | impl Component for ProductDetail { 35 | type Message = Msg; 36 | type Properties = Props; 37 | 38 | fn create(props: Self::Properties, link: ComponentLink) -> Self { 39 | link.send_message(Msg::GetProduct); 40 | 41 | Self { 42 | props, 43 | state: State { 44 | product: None, 45 | get_product_error: None, 46 | get_product_loaded: false, 47 | }, 48 | link, 49 | task: None, 50 | } 51 | } 52 | 53 | fn update(&mut self, message: Self::Message) -> ShouldRender { 54 | match message { 55 | Msg::GetProduct => { 56 | let handler = self 57 | .link 58 | .callback(move |response: api::FetchResponse| { 59 | let (_, Json(data)) = response.into_parts(); 60 | match data { 61 | Ok(product) => Msg::GetProductSuccess(product), 62 | Err(err) => Msg::GetProductError(err), 63 | } 64 | }); 65 | 66 | self.task = Some(api::get_product(self.props.id, handler)); 67 | true 68 | } 69 | Msg::GetProductSuccess(product) => { 70 | self.state.product = Some(product); 71 | self.state.get_product_loaded = true; 72 | true 73 | } 74 | Msg::GetProductError(error) => { 75 | self.state.get_product_error = Some(error); 76 | self.state.get_product_loaded = true; 77 | true 78 | } 79 | } 80 | } 81 | 82 | fn change(&mut self, _: Self::Properties) -> ShouldRender { 83 | false 84 | } 85 | 86 | fn view(&self) -> Html { 87 | if let Some(ref product) = self.state.product { 88 | html! { 89 |
90 | 91 |
{&product.name}
92 |
{&product.description}
93 |
{"$"}{&product.price}
94 | 95 |
96 | } 97 | } else if !self.state.get_product_loaded { 98 | html! { 99 |
100 |
101 |
{"Loading ..."}
102 |
103 | } 104 | } else { 105 | html! { 106 |
107 | {"Error loading product! :("} 108 |
109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/pages/home.rs: -------------------------------------------------------------------------------- 1 | use crate::api; 2 | use crate::components::ProductCard; 3 | use crate::types::{CartProduct, Product}; 4 | use anyhow::Error; 5 | use yew::format::Json; 6 | use yew::prelude::*; 7 | use yew::services::fetch::FetchTask; 8 | 9 | struct State { 10 | products: Vec, 11 | get_products_error: Option, 12 | get_products_loaded: bool, 13 | } 14 | 15 | #[derive(Properties, Clone)] 16 | pub struct Props { 17 | pub cart_products: Vec, 18 | pub on_add_to_cart: Callback, 19 | } 20 | 21 | pub struct Home { 22 | props: Props, 23 | state: State, 24 | link: ComponentLink, 25 | task: Option, 26 | } 27 | 28 | pub enum Msg { 29 | GetProducts, 30 | GetProductsSuccess(Vec), 31 | GetProductsError(Error), 32 | } 33 | 34 | impl Component for Home { 35 | type Message = Msg; 36 | type Properties = Props; 37 | 38 | fn create(props: Self::Properties, link: ComponentLink) -> Self { 39 | let products = vec![]; 40 | 41 | link.send_message(Msg::GetProducts); 42 | 43 | Self { 44 | props, 45 | state: State { 46 | products, 47 | get_products_error: None, 48 | get_products_loaded: false, 49 | }, 50 | link, 51 | task: None, 52 | } 53 | } 54 | 55 | fn update(&mut self, message: Self::Message) -> ShouldRender { 56 | match message { 57 | Msg::GetProducts => { 58 | self.state.get_products_loaded = false; 59 | let handler = 60 | self.link 61 | .callback(move |response: api::FetchResponse>| { 62 | let (_, Json(data)) = response.into_parts(); 63 | match data { 64 | Ok(products) => Msg::GetProductsSuccess(products), 65 | Err(err) => Msg::GetProductsError(err), 66 | } 67 | }); 68 | 69 | self.task = Some(api::get_products(handler)); 70 | true 71 | } 72 | Msg::GetProductsSuccess(products) => { 73 | self.state.products = products; 74 | self.state.get_products_loaded = true; 75 | true 76 | } 77 | Msg::GetProductsError(error) => { 78 | self.state.get_products_error = Some(error); 79 | self.state.get_products_loaded = true; 80 | true 81 | } 82 | } 83 | } 84 | 85 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 86 | self.props = props; 87 | true 88 | } 89 | 90 | fn view(&self) -> Html { 91 | let products: Vec = self 92 | .state 93 | .products 94 | .iter() 95 | .map(|product: &Product| { 96 | html! { 97 | 98 | } 99 | }) 100 | .collect(); 101 | 102 | if !self.state.get_products_loaded { 103 | html! { 104 |
105 |
106 |
{"Loading ..."}
107 |
108 | } 109 | } else if let Some(_) = self.state.get_products_error { 110 | html! { 111 |
112 | {"Error loading products! :("} 113 |
114 | } 115 | } else { 116 | html! { 117 |
{products}
118 | } 119 | } 120 | } 121 | } 122 | --------------------------------------------------------------------------------