Skip to content

Commit

Permalink
Suspense Support (yewstack#2212)
Browse files Browse the repository at this point in the history
* Make Html a Result.

* Fix tests.

* Implement Suspense.

* Schedule render when suspension is resumed.

* Shift children into a detached node.

* styled example.

* Update wording a little bit.

* Move hint to hint.

* Add some tests.

* Fix clippy.

* Add docs.

* Add to sidebar.

* Fix syntax highlight.

* Component -> BaseComponent.

* Html -> VNode, HtmlResult = RenderResult<Html>.

* Suspendible Function Component.

* Add a method to create suspension from futures.

* Revert extra changes.

* Fix tests.

* Update documentation.

* Switch to custom trait to make test reliable.

* Fix file permission.

* Fix docs.

* Remove log.

* Fix file permission.

* Fix component name error.

* Make Suspension a future.
  • Loading branch information
futursolo committed Jan 5, 2022
1 parent eb23b32 commit ac3af0a
Show file tree
Hide file tree
Showing 55 changed files with 1,979 additions and 424 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -31,6 +31,7 @@ members = [
"examples/two_apps",
"examples/webgl",
"examples/web_worker_fib",
"examples/suspense",

# Tools
"tools/changelog",
Expand Down
2 changes: 1 addition & 1 deletion examples/function_todomvc/src/components/entry.rs
Expand Up @@ -2,7 +2,7 @@ use crate::hooks::use_bool_toggle::use_bool_toggle;
use crate::state::Entry as Item;
use web_sys::{HtmlInputElement, MouseEvent};
use yew::events::{Event, FocusEvent, KeyboardEvent};
use yew::{function_component, html, Callback, Classes, Properties, TargetCast};
use yew::prelude::*;

#[derive(PartialEq, Properties, Clone)]
pub struct EntryProps {
Expand Down
2 changes: 1 addition & 1 deletion examples/function_todomvc/src/components/filter.rs
@@ -1,5 +1,5 @@
use crate::state::Filter as FilterEnum;
use yew::{function_component, html, Callback, Properties};
use yew::prelude::*;

#[derive(PartialEq, Properties)]
pub struct FilterProps {
Expand Down
2 changes: 1 addition & 1 deletion examples/function_todomvc/src/components/header_input.rs
@@ -1,6 +1,6 @@
use web_sys::HtmlInputElement;
use yew::events::KeyboardEvent;
use yew::{function_component, html, Callback, Properties, TargetCast};
use yew::prelude::*;

#[derive(PartialEq, Properties, Clone)]
pub struct HeaderInputProps {
Expand Down
2 changes: 1 addition & 1 deletion examples/function_todomvc/src/components/info_footer.rs
@@ -1,4 +1,4 @@
use yew::{function_component, html};
use yew::prelude::*;

#[function_component(InfoFooter)]
pub fn info_footer() -> Html {
Expand Down
2 changes: 1 addition & 1 deletion examples/function_todomvc/src/main.rs
@@ -1,7 +1,7 @@
use gloo::storage::{LocalStorage, Storage};
use state::{Action, Filter, State};
use strum::IntoEnumIterator;
use yew::{classes, function_component, html, use_effect_with_deps, use_reducer, Callback};
use yew::prelude::*;

mod components;
mod hooks;
Expand Down
19 changes: 19 additions & 0 deletions examples/suspense/Cargo.toml
@@ -0,0 +1,19 @@
[package]
name = "suspense"
version = "0.1.0"
edition = "2018"
license = "MIT OR Apache-2.0"

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

[dependencies]
yew = { path = "../../packages/yew" }
gloo-timers = { version = "0.2.2", features = ["futures"] }
wasm-bindgen-futures = "0.4"
wasm-bindgen = "0.2"

[dependencies.web-sys]
version = "0.3"
features = [
"HtmlTextAreaElement",
]
10 changes: 10 additions & 0 deletions examples/suspense/README.md
@@ -0,0 +1,10 @@
# Suspense Example

[![Demo](https://img.shields.io/website?label=demo&url=https%3A%2F%2Fexamples.yew.rs%2Fsuspense)](https://examples.yew.rs/suspense)

This is an example that demonstrates `<Suspense />` support.

## Concepts

This example shows that how `<Suspense />` works in Yew and how you can
create hooks that utilises suspense.
11 changes: 11 additions & 0 deletions examples/suspense/index.html
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew Suspense Demo</title>

<link data-trunk rel="sass" href="index.scss" />
</head>

<body></body>
</html>
71 changes: 71 additions & 0 deletions examples/suspense/index.scss
@@ -0,0 +1,71 @@
html, body {
font-family: sans-serif;

margin: 0;
padding: 0;

background-color: rgb(237, 244, 255);
}

.layout {
height: 100vh;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.content {
height: 600px;
width: 600px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

border-radius: 4px;
box-shadow: 0 0 5px 0 black;

background: white;
}

.content-area {
width: 350px;
height: 500px;

display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

textarea {
width: 300px;
height: 300px;
font-size: 15px;
}

.action-area {
padding-top: 40px;
}

button {
color: white;
height: 50px;
width: 300px;
font-size: 20px;
background-color: rgb(88, 164, 255);
border-radius: 5px;
border: none;
}

.hint {
padding-top: 20px;

font-size: 12px;

text-align: center;

color: rgb(100, 100, 100);
}
60 changes: 60 additions & 0 deletions examples/suspense/src/main.rs
@@ -0,0 +1,60 @@
use web_sys::HtmlTextAreaElement;
use yew::prelude::*;

mod use_sleep;

use use_sleep::use_sleep;

#[function_component(PleaseWait)]
fn please_wait() -> Html {
html! {<div class="content-area">{"Please wait 5 Seconds..."}</div>}
}

#[function_component(AppContent)]
fn app_content() -> HtmlResult {
let resleep = use_sleep()?;

let value = use_state(|| "I am writing a long story...".to_string());

let on_text_input = {
let value = value.clone();

Callback::from(move |e: InputEvent| {
let input: HtmlTextAreaElement = e.target_unchecked_into();

value.set(input.value());
})
};

let on_take_a_break = Callback::from(move |_| (resleep.clone())());

Ok(html! {
<div class="content-area">
<textarea value={value.to_string()} oninput={on_text_input}></textarea>
<div class="action-area">
<button onclick={on_take_a_break}>{"Take a break!"}</button>
<div class="hint">{"You can take a break at anytime"}<br />{"and your work will be preserved."}</div>
</div>
</div>
})
}

#[function_component(App)]
fn app() -> Html {
let fallback = html! {<PleaseWait />};

html! {
<div class="layout">
<div class="content">
<h1>{"Yew Suspense Demo"}</h1>
<Suspense fallback={fallback}>
<AppContent />
</Suspense>
</div>
</div>
}
}

fn main() {
yew::start_app::<App>();
}
39 changes: 39 additions & 0 deletions examples/suspense/src/use_sleep.rs
@@ -0,0 +1,39 @@
use std::rc::Rc;
use std::time::Duration;

use gloo_timers::future::sleep;
use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};

#[derive(PartialEq)]
pub struct SleepState {
s: Suspension,
}

impl SleepState {
fn new() -> Self {
let s = Suspension::from_future(async {
sleep(Duration::from_secs(5)).await;
});

Self { s }
}
}

impl Reducible for SleepState {
type Action = ();

fn reduce(self: Rc<Self>, _action: Self::Action) -> Rc<Self> {
Self::new().into()
}
}

pub fn use_sleep() -> SuspensionResult<Rc<dyn Fn()>> {
let sleep_state = use_reducer(SleepState::new);

if sleep_state.s.resumed() {
Ok(Rc::new(move || sleep_state.dispatch(())))
} else {
Err(sleep_state.s.clone())
}
}

0 comments on commit ac3af0a

Please sign in to comment.