-
-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add example to fetch data at the Component level
- Loading branch information
Showing
7 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
|
||
[package] | ||
name = "fetch-data-component" | ||
version = "0.1.0" | ||
authors = [ "Jovansonlee Cesar <[email protected]>" ] | ||
license = "MIT" | ||
edition = "2021" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
|
||
[dependencies] | ||
sauron = { path = "../../" } | ||
console_error_panic_hook = { version = "0.1" } | ||
serde = { version = "1.0", features = ["serde_derive"]} | ||
serde_json = "1.0" | ||
log = "0.4" | ||
console_log = {version="0.2", features = ["color"]} | ||
wasm-bindgen-futures = "0.4.32" | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
## Fetching data from a component | ||
|
||
This is an example derived from the fetch-data examples, but instead of fetching the data | ||
from the Application, we are fetching the data at the Component level. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/bin/bash | ||
|
||
set -v | ||
|
||
if ! type wasm-pack > /dev/null; then | ||
echo "wasm-pack is not installed" | ||
cargo install wasm-pack | ||
fi | ||
|
||
if ! type basic-http-server > /dev/null; then | ||
echo "basic-http-server is not installed" | ||
cargo install basic-http-server | ||
fi | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
|
||
<html> | ||
<head> | ||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/> | ||
<title>Fetch data from component</title> | ||
</head> | ||
<body> | ||
<script type=module> | ||
import init from './pkg/fetch_data_component.js'; | ||
async function start(){ | ||
await init().catch(console.error); | ||
} | ||
await start(); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/bin/bash | ||
|
||
set -v | ||
|
||
. ./bootstrap.sh | ||
|
||
wasm-pack build --target web --release -- &&\ | ||
|
||
basic-http-server ./ -a 0.0.0.0:4001 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
|
||
use sauron::{dom::Http, js_sys::TypeError, jss, *}; | ||
use serde::Deserialize; | ||
|
||
|
||
const DATA_URL: &str = "https://reqres.in/api/users"; | ||
const PER_PAGE: i32 = 4; | ||
|
||
#[derive(Debug)] | ||
pub enum Msg { | ||
NextPage, | ||
PrevPage, | ||
ReceivedData(Data), | ||
JsonError(serde_json::Error), | ||
RequestError(TypeError), | ||
} | ||
|
||
pub struct Fetcher { | ||
page: i32, | ||
data: Data, | ||
error: Option<String>, | ||
} | ||
|
||
#[derive(Deserialize, Debug, PartialEq, Clone, Default)] | ||
pub struct Data { | ||
page: i32, | ||
per_page: i32, | ||
total: i32, | ||
total_pages: i32, | ||
data: Vec<User>, | ||
} | ||
|
||
#[derive(Deserialize, PartialEq, Debug, Clone)] | ||
pub struct User { | ||
id: i32, | ||
email: String, | ||
first_name: String, | ||
last_name: String, | ||
avatar: String, | ||
} | ||
|
||
impl Fetcher { | ||
pub fn new() -> Self { | ||
Self { | ||
page: 1, | ||
data: Data::default(), | ||
error: None, | ||
} | ||
} | ||
|
||
fn fetch_page(&self) -> Effects<Msg, ()> { | ||
let url = format!("{}?page={}&per_page={}", DATA_URL, self.page, PER_PAGE); | ||
|
||
Effects::with_local_async([ | ||
async move { | ||
match Http::fetch_text(&url).await { | ||
Ok(v) => match serde_json::from_str(&v) { | ||
Ok(data1) => Msg::ReceivedData(data1), | ||
Err(err) => Msg::JsonError(err), | ||
}, | ||
Err(e) => Msg::RequestError(e), | ||
} | ||
} | ||
]) | ||
} | ||
} | ||
|
||
impl Component<Msg,()> for Fetcher { | ||
fn init(&mut self) -> Effects<Msg, ()> { | ||
self.fetch_page() | ||
} | ||
|
||
fn view(&self) -> Node<Msg> { | ||
node! { | ||
<div> | ||
<div class="some-class" id="some-id" {attr("data-id", 1)}> | ||
<input class="prev_page" type="button" | ||
disabled={self.page <= 1} | ||
value="<< Prev Page" | ||
on_click=|_| { | ||
trace!("Button is clicked"); | ||
Msg::PrevPage | ||
} | ||
/> | ||
{text(format!("Page: {}", self.page))} | ||
<input class="next_page" type="button" | ||
disabled={self.page >= self.data.total_pages} | ||
value="Next Page >>" | ||
on_click=|_|{ | ||
trace!("Button is clicked"); | ||
Msg::NextPage | ||
} | ||
/> | ||
</div> | ||
<div> | ||
{ | ||
for user in self.data.data.iter(){ | ||
node!{ | ||
<ul> | ||
<li>{text(&user.id)}</li> | ||
<li>{text(&user.email)}</li> | ||
<li>{text(&user.first_name)}</li> | ||
<li><img src=&user.avatar/></li> | ||
</ul> | ||
} | ||
} | ||
} | ||
</div> | ||
<footer class="error"> | ||
{if let Some(error) = &self.error { | ||
text(error) | ||
} else { | ||
text!("") | ||
}} | ||
</footer> | ||
</div> | ||
} | ||
} | ||
|
||
fn update(&mut self, msg: Msg) -> Effects<Msg, ()> { | ||
trace!("App is updating from msg: {:?}", msg); | ||
match msg { | ||
Msg::NextPage => { | ||
if self.page < self.data.total_pages { | ||
self.page += 1; | ||
self.fetch_page() | ||
} else { | ||
Effects::none() | ||
} | ||
} | ||
Msg::PrevPage => { | ||
if self.page > 1 { | ||
self.page -= 1; | ||
} | ||
self.fetch_page() | ||
} | ||
Msg::ReceivedData(data1) => { | ||
self.data = data1; | ||
Effects::none() | ||
} | ||
Msg::JsonError(err) => { | ||
trace!("Error fetching users! {:#?}", err); | ||
self.error = Some(format!("There was an error fetching the page: {:?}", err)); | ||
Effects::none() | ||
} | ||
Msg::RequestError(type_error) => { | ||
trace!("Error requesting the page: {:?}", type_error); | ||
self.error = Some(format!( | ||
"There was an error fetching the page: {:?}", | ||
type_error | ||
)); | ||
Effects::none() | ||
} | ||
} | ||
} | ||
|
||
fn stylesheet() -> Vec<String> { | ||
vec![jss! { | ||
"body": { | ||
font_family: "Fira Sans, Courier New, Courier, Lucida Sans Typewriter, Lucida Typewriter, monospace", | ||
} | ||
}] | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
#![deny(warnings)] | ||
use sauron::*; | ||
use fetcher::Fetcher; | ||
|
||
#[macro_use] | ||
extern crate log; | ||
|
||
mod fetcher; | ||
|
||
|
||
#[derive(Debug)] | ||
pub enum Msg { | ||
FetcherMsg(fetcher::Msg), | ||
} | ||
|
||
pub struct App { | ||
fetcher: Fetcher, | ||
} | ||
|
||
impl App { | ||
pub fn new() -> Self { | ||
App { | ||
fetcher: Fetcher::new() | ||
} | ||
} | ||
|
||
} | ||
|
||
impl Application<Msg> for App { | ||
fn init(&mut self) -> Cmd<Self, Msg> { | ||
console_log::init_with_level(log::Level::Trace).unwrap(); | ||
Cmd::from(self.fetcher.init().map_msg(Msg::FetcherMsg)) | ||
} | ||
|
||
fn view(&self) -> Node<Msg> { | ||
node! { | ||
<main> | ||
<h1>This is the app</h1> | ||
{self.fetcher.view().map_msg(Msg::FetcherMsg)} | ||
</main> | ||
} | ||
} | ||
|
||
fn update(&mut self, msg: Msg) -> Cmd<Self, Msg> { | ||
match msg{ | ||
Msg::FetcherMsg(fmsg) => Cmd::from(self.fetcher.update(fmsg).map_msg(Msg::FetcherMsg)) | ||
} | ||
} | ||
|
||
fn stylesheet() -> Vec<String> { | ||
Fetcher::stylesheet() | ||
} | ||
} | ||
|
||
#[wasm_bindgen(start)] | ||
pub fn main() { | ||
console_error_panic_hook::set_once(); | ||
Program::mount_to_body(App::new()); | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_json() { | ||
let json = r#" | ||
{"page":1,"per_page":3,"total":12,"total_pages":4,"data":[{"id":1,"email":"[email protected]","first_name":"George","last_name":"Bluth","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"},{"id":2,"email":"[email protected]","first_name":"Janet","last_name":"Weaver","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"},{"id":3,"email":"[email protected]","first_name":"Emma","last_name":"Wong","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/olegpogodaev/128.jpg"}]} | ||
"#; | ||
println!("json: {}", json); | ||
let data: Result<Data, _> = serde_json::from_str(json); | ||
println!("data: {:#?}", data); | ||
assert!(data.is_ok()); | ||
} | ||
} |