-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suspense Support #2212
Suspense Support #2212
Conversation
I imagine that this needs a lot of tests. So I submitted the PR as I add tests so anyone who wants to give it a shot can have a try and see if the design have flaws / can be improved. Mainly now |
Visit the preview URL for this PR (updated for commit 5a376af): https://yew-rs--pr2212-suspense-7k6vaamf.web.app (expires Wed, 12 Jan 2022 11:05:27 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 |
If there's a |
These 2 are used for different purposes.
A hook that suspends a component returns a |
I have added tests and docs to this PR and now it's ready for review. |
Sorry this PR will probably be a hostage until we actually release 0.19 |
I really don't like this change
This means that every As a side note, can we not call it "Suspense"? It seems like everything in Yew is ripped off from React and even in React, Suspense name isn't a clear representation of what it does. |
I also wish there's a better way to do this. However, Suspense requires an early interruption of the function component rendering flow. This is done via
There's no fundamental difference between these 2 approaches as As for A: type Html = RenderResult<VNode>;
#[function_component(Comp)]
fn comp() -> Html {
html! { ... }
} B: type HtmlResult = RenderResult<Html>;
#[function_component(Comp)]
fn comp() -> HtmlResult {
Ok(html! { ... })
} C: // Magic in Procedural Macro, Suspends with custom syntax.
#[function_component(Comp)]
fn comp() -> Html {
html! { ... }
} After evaluating the design, I think B's design is the most "correct" design but likely to cause difficulty in migration. Hence, I have picked A. As a bonus point, I was able to fixed all examples with very little changes (in ~5 mins).
I think suspense naming is appropriate here.
One point that should be taken into consideration is that: In addition, none of these features are straightforward to understand. Which kind of achieves the same effect of saying that this is "inspired" by React. |
Instead of returning struct VSuspense {
fallback: VNode,
content: VNode,
}
enum VNode {
// ...
VSuspense(VSuspense)
}
// in Suspense component
#[derive(Properties, PartialEq)]
struct SusProps {
fallback: Html,
children: Html,
}
#[function_component(Suspense)]
fn sus(props: &SusProps) -> Html {
VNode::VSuspense(VSuspense {
fallback: props.fallback.clone(),
content: props.children.clone()
})
}
// somewhere else
let fallback = html! { <Fallback /> };
html! {
<Suspense {fallback}>
<div>content</div>
</Suspense> We could also implement the |
The current implementation already has a The place that actually needs Please see my previous comment about why |
I have mixed views on this feature. And I am writing this while i looked only to the modified examples code, so pardon me if some comments are not well-informed. ProsIt will let us write less boilerplate in prod code that is like this: let bob = match fetched_bob {
Some(some) => some,
None => return html!{<MyFallback />},
} Though to be honest the upper thing could be solved simply by getting let else statement stable https://rust-lang.github.io/rfcs/3137-let-else.html Other than that I don't see the benefit of specifying the fallback on the parent. Though can the suspense cascade up the tree more than once? Cons?I ended up rambling in cons so you can tldr it at the end While inherently its not a bad thing, and definitely it will lessen the amount of boilerplate error handling, I think it a little drives away from the rust model where all errors should be explicitly handled. But then again it kind of still makes sense because its like collecting all the errors at the root DOM done and not rendering anything faulty. TLDR: I see only pros with the feature idea. I don't see any cons that actually forces anyone into a bad scenario. |
Regarding the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went through the PR, now i realize i was mixing up Suspense with error boundaries a little, sorry for that.
I am rejecting until proper steps are made towards A
, B
or C
options, depending on which one we decide to use in this PR discussion.
# Conflicts: # packages/yew-macro/src/function_component.rs
After some consideration, I have decided not to choose Option D. For the following reasons:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's files committed (shown in the diff) but without any changes. Can you fix that?
// When a component ends with Internal, it will become possessive. | ||
// InternalsInternal instead of InternalInternal | ||
provider_name_str.push_str("sInternal"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't cover the following case: InternalsInternal
. That will become InternalssInternal
. Besides, why even bother with the provider name? It's not like it really matters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InternalsInternal
's Internal will become InternalsInternalsInternal
.
// no longer possible? | ||
// #[::yew::function_component(ConstGenerics)] | ||
// fn const_generics<const N: ::std::primitive::i32>() -> ::yew::Html { | ||
// ::yew::html! { | ||
// <div> | ||
// { N } | ||
// </div> | ||
// } | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happened here? Seems like this PR broke something since the CI passes on master.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const generics are not possible in functions and the updated implementation now uses a function.
hook should return a `Err(Suspension)` and users should unwrap it with | ||
`?` in which it will be converted into `Html`. | ||
|
||
```rust ,ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we not ignore this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not ignoring it would require including a complete hook implementation on every snippet.
I have included a complete example below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder how long will it take for docusaurus to finally merge this in facebook/docusaurus#5783
Then we could include all needed code and just hide it.
website/docs/concepts/suspense.md
Outdated
can use a function component as a Higher-Order-Component to | ||
achieve suspense-based data fetching. | ||
|
||
```rust, ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, let's not ignore here the snippets
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well looks fine to me, though i fully admit that due to me not contributing that much code wise i can miss things.
# Conflicts: # packages/yew/src/html/component/scope.rs # packages/yew/src/lib.rs # website/docs/concepts/function-components/custom-hooks.mdx # website/docs/concepts/function-components/pre-defined-hooks.mdx
I wonder if suspense is a good feature to have considering you could reproduce this error very easily as using a (but i need to test this) |
There's a test covering early return, and it's not reproducible in the test case. I think the issue you are experiencing is something different. Because the number of hooks is not limited at the moment. The error mentioned in the discord (where hooks fail to downcast): https://github.com/yewstack/yew/blob/master/packages/yew/src/functional/hooks/mod.rs#L72 |
# Conflicts: # packages/yew/tests/use_state.rs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
I will admit, I didn't inspect literally everything but it this should be good. If something comes up, it can be fixed later on.
/// Move elements from one parent to another parent. | ||
/// This is currently only used by `VSuspense` to preserve component state without detaching | ||
/// (which destroys component state). | ||
/// Prefer `detach` then apply if possible. | ||
fn shift(&self, previous_parent: &Element, next_parent: &Element, next_sibling: NodeRef); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you comment why this can't reuse VNode::move_before
? There is a slight difference in that the next_sibling
is a NodeRef
instead of the dereferenced Option<&Node>
but I don't immediately see how that matters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move_before
assumes previous position and next position are under the same parent and hence does not attempt to update ComponentState
where as shift
has a different parent and hence is updated via push_component_update
for VComp.
* 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.
Description
This pull request introduces an experimental Suspense support.
Summary:
HtmlResult
as an alias toRenderResult<Html>
.RenderError
is added withSuspended
being the only variant at the moment.<Suspense />
component is added that renders a fallback UI when receiving aSuspension
.Suspension
andSuspensionHandle
type to suspend component rendering.VSuspense
variant on virtual dom to facilitate special rendering behaviour (preserves component state when suspended).Checklist
cargo make pr-flow