Skip to content

Commit

Permalink
feat: add example to fetch data at the Component level
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanceras committed Feb 9, 2024
1 parent 635d34b commit f1d56fe
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 0 deletions.
21 changes: 21 additions & 0 deletions examples/fetch-data-component/Cargo.toml
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"


4 changes: 4 additions & 0 deletions examples/fetch-data-component/README.md
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.
14 changes: 14 additions & 0 deletions examples/fetch-data-component/bootstrap.sh
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

16 changes: 16 additions & 0 deletions examples/fetch-data-component/index.html
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>
9 changes: 9 additions & 0 deletions examples/fetch-data-component/serve.sh
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
165 changes: 165 additions & 0 deletions examples/fetch-data-component/src/fetcher.rs
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",
}
}]
}
}

75 changes: 75 additions & 0 deletions examples/fetch-data-component/src/lib.rs
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());
}
}

0 comments on commit f1d56fe

Please sign in to comment.