Skip to content

Commit

Permalink
docs: added new example for fetching data
Browse files Browse the repository at this point in the history
Addresses #96.
  • Loading branch information
arctic-hen7 committed Dec 30, 2021
1 parent 5c338aa commit 6b08ffe
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 2 deletions.
14 changes: 13 additions & 1 deletion docs/0.3.x/en-US/server-communication.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,16 @@ In the last few years, a new technology has sprung up that allows you to run ind

## Querying a Server

You have a few options if you want to query a server from client-side code. You can use an high-level module, like [reqwest](https://docs.rs/reqwest) (which supports Wasm), or you can use the underlying browser Fetch API directly (which entails turning JavaScript types into Rust types). We recommend the first approach, but an example of the second can be found in the Perseus code [here](https://github.com/arctic-hen7/perseus/blob/61dac01b838df23cc0f33b0d65fcb7bf5f252770/packages/perseus/src/shell.rs#L19-L65).
### At Build-Time

It's fairly trivial to communicate with a server at build-time in Perseus, which allows you to fetch data when you build your app, and then your users don't have to do as much work. You can also use other strategies to fetch data [at request-time](:strategies/request-state) if needed. Right now, it's best to use a blocking API to make requests on the server, which you can do with libraries like [`ureq`](https://docs.rs/ureq).

### In the Browser

In some cases, it's just not possible to fetch the data you need on the server, and the client needs to fetch it themselves. This is often the case in [exported](:exporting) apps. This is typically done with the browser's inbuilt Fetch API, which is conveniently wrapped by [`reqwasm`](https://docs.rs/reqwasm).

However, if you try to request from a public API in this way, you may run into problems with [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), which can be very confusing, especially if you're not used to web development! The simple explanation of this is that CORS is a *thing* that browsers use to make sure your code can't send requests to servers that haven't allowed it (as in your code specifically). If you're querying your own server and getting this problem, make sure to set the `Access-Control-Allow-Origin` header to allow your site to make requests (see [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for more details). However, if a public API hasn't set this, you're up the creek! In these cases, it's best to query through your own server or through one of Perseus' rendering strategies (if possible).

## Example

This can be confusing stuff, especially because it's different on the client and the server, so you may want to take a look at [this example](https://github.com/arctic-hen7/perseus/tree/main/examples/fetching) in the Perseus repo, which gets the IP address of the machine that built it, and then shows the user a message hosted with a [static alias](:static-content).
14 changes: 13 additions & 1 deletion docs/next/en-US/server-communication.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,16 @@ In the last few years, a new technology has sprung up that allows you to run ind

## Querying a Server

You have a few options if you want to query a server from client-side code. You can use an high-level module, like [reqwest](https://docs.rs/reqwest) (which supports Wasm), or you can use the underlying browser Fetch API directly (which entails turning JavaScript types into Rust types). We recommend the first approach, but an example of the second can be found in the Perseus code [here](https://github.com/arctic-hen7/perseus/blob/61dac01b838df23cc0f33b0d65fcb7bf5f252770/packages/perseus/src/shell.rs#L19-L65).
### At Build-Time

It's fairly trivial to communicate with a server at build-time in Perseus, which allows you to fetch data when you build your app, and then your users don't have to do as much work. You can also use other strategies to fetch data [at request-time](:strategies/request-state) if needed. Right now, it's best to use a blocking API to make requests on the server, which you can do with libraries like [`ureq`](https://docs.rs/ureq).

### In the Browser

In some cases, it's just not possible to fetch the data you need on the server, and the client needs to fetch it themselves. This is often the case in [exported](:exporting) apps. This is typically done with the browser's inbuilt Fetch API, which is conveniently wrapped by [`reqwasm`](https://docs.rs/reqwasm).

However, if you try to request from a public API in this way, you may run into problems with [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), which can be very confusing, especially if you're not used to web development! The simple explanation of this is that CORS is a *thing* that browsers use to make sure your code can't send requests to servers that haven't allowed it (as in your code specifically). If you're querying your own server and getting this problem, make sure to set the `Access-Control-Allow-Origin` header to allow your site to make requests (see [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for more details). However, if a public API hasn't set this, you're up the creek! In these cases, it's best to query through your own server or through one of Perseus' rendering strategies (if possible).

## Example

This can be confusing stuff, especially because it's different on the client and the server, so you may want to take a look at [this example](https://github.com/arctic-hen7/perseus/tree/main/examples/fetching) in the Perseus repo, which gets the IP address of the machine that built it, and then shows the user a message hosted with a [static alias](:static-content).
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ These examples are all fully self-contained, and do not serve as examples in the
- i18n -- a simple app that showcases internationalization in particular
- Tiny -- the smallest Perseus can get, the _Hello World!_ example
- Plugins -- an example of creating and integrating plugins into Perseus
- Fetching -- an example of fetching data at build time and in the browser with `reqwasm`
2 changes: 2 additions & 0 deletions examples/fetching/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

.perseus/
15 changes: 15 additions & 0 deletions examples/fetching/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "perseus-example-fetching"
version = "0.3.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
perseus = { path = "../../packages/perseus", features = [ "hydrate" ] }
sycamore = "0.7"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
ureq = "2"
reqwasm = "0.4"
wasm-bindgen-futures = "0.4"
10 changes: 10 additions & 0 deletions examples/fetching/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
</body>
</html>
1 change: 1 addition & 0 deletions examples/fetching/message.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is the message!
58 changes: 58 additions & 0 deletions examples/fetching/src/index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use perseus::{Html, RenderFnResultWithCause, Template};
use serde::{Deserialize, Serialize};
use sycamore::prelude::*;

#[derive(Serialize, Deserialize)]
pub struct IndexProps {
ip: String,
}

#[perseus::template(IndexPage)]
#[component(IndexPage<G>)]
pub fn index_page(IndexProps { ip }: IndexProps) -> View<G> {
// This will store the message that we get
// Until we've got it, we'll display `fetching...`
let message = Signal::new("fetching...".to_string());

// This will only run in the browser
// `reqwasm` wraps browser-specific APIs, so we don't want it running on the server
if G::IS_BROWSER {
// Spawn a `Future` on this thread to fetch the data
// Don't worry, this doesn't need to be sent to JavaScript for execution
//
// We want to access the `message` `Signal`, so we'll clone it in (and then we need `move` because this has to be `'static`)
wasm_bindgen_futures::spawn_local(cloned!(message => async move {
// This interface may seem weird, that's because it wraps the browser's Fetch API
// We request from a local path here because of CORS restrictions (see the book)
let body = reqwasm::http::Request::get("/message")
.send()
.await
.unwrap()
.text()
.await
.unwrap();
message.set(body);
}));
}

view! {
p { (format!("IP address of the builder was: {}", ip)) }
p { (format!("The message at `/message` is: {}", message.get())) }
}
}

pub fn get_template<G: Html>() -> Template<G> {
Template::new("index")
.build_state_fn(get_build_state)
.template(index_page)
}

#[perseus::autoserde(build_state)]
pub async fn get_build_state(
_path: String,
_locale: String,
) -> RenderFnResultWithCause<IndexProps> {
// This just gets the IP address of the machine that built the app
let body: String = ureq::get("https://api.ipify.org").call()?.into_string()?;
Ok(IndexProps { ip: body })
}
16 changes: 16 additions & 0 deletions examples/fetching/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
mod index;

use perseus::define_app;
define_app! {
templates: [
index::get_template::<G>()
],
error_pages: perseus::ErrorPages::new(|url, status, err, _| {
sycamore::view! {
p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) }
}
}),
static_aliases: {
"/message" => "message.txt"
}
}

0 comments on commit 6b08ffe

Please sign in to comment.