-
Notifications
You must be signed in to change notification settings - Fork 115
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
xilem_web: Add EventHandler
trait and an additional defer
event handler, shown in a new fetch
example
#440
base: main
Are you sure you want to change the base?
Conversation
…defer handler, with requiring an action type for the callback
Co-authored-by: Markus Kohlhase <[email protected]>
After a short chat with Daniel, I think I get what he meant with declarative, so taking the fetch example: fork(
input(()).on_input(|state: &mut AppState, ev: web_sys::Event| {
let count = event_target_value(&ev).parse::<usize>().unwrap_or(0);
state.cats_to_fetch = count;
}),
(state.cats_to_fetch > 0).then_some(fetch(
format!(
"https://api.thecatapi.com/v1/images/search?limit={}",
state.cats_to_fetch
),
|state, fetch_result| { state.cats = do_something_with_result(fetch_result); }
)),
), this could be an alternative, and I see appeal in it (e.g. for the above, when the url changes, the current request will be canceled, and a new one started, and when Though I think this has some implications, the above ( I think such behavior could be achieved similarly as the fork(
input(()).on_input(|state: &mut AppState, ev: web_sys::Event| {
let count = event_target_value(&ev).parse::<usize>().unwrap_or(0);
state.cats_to_fetch = count;
}),
(state.cats_to_fetch > 0).then_some(rerun_on_change(
state.cats_to_fetch,
|count| fetch_cats(count),
|state, output| {
state.cats = do_something_with_result(output);
},
)),
), I'm not sure, I see value for both approaches (the one of this PR, see also this comment, and the ideas in this comment (more declarative)). |
Event: 'static, | ||
FOut: Message, | ||
F: Future<Output = FOut> + 'static, | ||
FF: Fn(&mut State, Event) -> F + 'static, |
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 think this could also be
FF: Fn(&mut State, Event) -> F + 'static, | |
FF: Fn(&mut State, Event) -> Option<F> + 'static, |
which would give the user more control, if they want to handle the event or not, thus maybe make the blocking/once stuff above a non issue, as the user could handle this with more control.
I think the parallel/serial(queue) thing explained in the comment above may still be useful though for configuration.
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.
But why would you call defer
if you do not want to return a Future
?
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.
... it would be like this, right?
|state, ev|{
if state.value == some_value {
None
} else {
Some(async { /* */ })
}
}
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.
Yes
Yes, rerun_on_change (or whatever the name becomes would match my intention). I do think there is value in being able to launch ad-hoc tasks, but I'd like to push as far on the declarative approach as possible, because I do think it has some real advantages. I'm not sure whether I'm going to review this PR. |
Yeah, I think I'm going to check this as well, as I see value for it either way, including specialized views, like the |
This is the `rerun_on_change` view described in #440, I named it `MemoizedAwait` as I think that fits its functionality. It got also additional features `debounce_ms` (default `0`) and `reset_debounce_on_update` (default `true`) as I think that's quite useful, in case a lot of updates are happening. When `reset_debounce_on_update == false`, `debounce` is more a throttle than an actual debounce. This also adds a more heavily modified version of the example added in #427, also showing `OneOf` in xilem_web (which didn't have an example yet). This *could* be considered as alternative to #440 (at the very least the example) but those approaches could also well live next to each other. --------- Co-authored-by: Markus Kohlhase <[email protected]>
Would it be possible to rebase/merge latest into this branch? I really want to try this PR out, but I'm dependent on a few things which were added since it was opened, and don't know how to fix the merge conflicts myself. |
Merged main into this, there happened quite a bit more than I thought... I've moved the example to "fetch_event_handler" |
Nice! I'm trying it out but getting an error. My code looks like this: ul(li(button("Node").on_click(defer(
|_: &mut State, _| async { Api::default().node().await.unwrap() },
|state: &mut State, node| state.view = View::Node { node },
)))), Which I think is basically just: button("foo").on_click(defer(
|_, _| async_bar(),
|state: &mut State, results| state.results = results,
)) I'm getting this error in the console when I click the button:
All of which seems to be from within xilem, so I don't think my code is the culprit. Without using this branch, I have a two-step process where I set an enum on the state to a variant that represents a pending request:
And then in the update function:
Which is working. |
Is that with a debug build? I wonder if there could be a more detailed error-trace (type erasure of the The type of a Edit: Ah I have a feeling what it could be, and it isn't encouraging (or needs at least more fundamental changes in the internal |
Or in other words: I think I need to rewrite #158 to be compatible with the new xilem_core to keep that compiler-time-optimization (and I hope we can remove that workaround with type-erasure when the new trait-solver is finished, as that would guarantee a fully static view again, but I doubt it when the app gets really big) |
In the meantime I would recommend using |
I came up with a minimal reproduction, and you're right, it looks like it isn't actually related to this branch. Here's the repro:
Clicking the button gives the same error:
Should I open a separate issue for this? |
I thought about it already, yeah, ideally with a minimal repro. I think something like this may already be enough (not yet tested though): fn component<State>(condition: bool) -> impl DomView<State> {
if condition {
div(()).boxed()
} else {
div("different child type").boxed()
}
} |
>.< darn I haven't read that far, looks quite similar as my one, and confirms my suspicion. |
@Philipp-M Yup, that fixed it! I checked out #505 locally and merged in #440 (since I'm trying out |
And, to be clear, |
That's also my thinking with it, I do think the declarative way (i.e. |
I definitely agree, and in fact, I think it's possible that I might wind up using both |
…om `AnyView` (#505) This should fix the issue described in #440 by adding the type of the `Children` `ViewSequence` to the type of element, which results in unique types, so `AnyView` should not cause issues anymore. Fortunately there's no regression with the compilation times here, so an `AnyViewSequence` doesn't seem to be necessary in that case.
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.
To be honest, I can't really keep track of the PR right now.
Maybe I'll have time to read through it again at the weekend.
But before it comes to a blockade, please talk to me directly about it again, maybe I can help specifically.
I'm not a xilem developer - just a user - so not sure if it's ok for me to write here, but I like this API and I'd really like to see this merged :) |
I think @Philipp-M is busy with other things at the moment (like me), but I would definitely be willing to help |
Adds an
EventHandler
trait, which can be used everywhere, where an event is expected to be invoked, likebutton("click me").on_click(impl EventHandler)
.This trait is implemented by
T: Fn(&mut State, Event) -> Action
and a newDeferEventHandler
which takes a callback returning a future, which after being resolved is given to another callback to modify the app state based on the output of the future.The new
DeferEventHandler
is demonstrated in a newfetch
example, which is a modified version of #427 (and thus also an alternative to that PR).The
EventHandler
trait may be moved toxilem_core
as I think it's generic enough to be able to be used in different contexts as well.Though since it uses a blanket impl currently for
F: Fn(&mut State, Event) -> Action + 'static
, it would be necessary to writebutton("label", |data: &mut State, ()| {})
when we want to use it in thebutton
, as we can't easily supportF: Fn(&mut State) -> Action + 'static
whereEvent = ()
, since those impls intersect. That may be solved by hardcoding event types for the impls, though I'm not sure if that's a good solution (and the orphan rule would make this even more complex).This is less an issue in xilem_web since all event functions have a payload, so there's no
Event = ()
currently.It may or not be possible to have
EventHandler<Event>: View
, as theEventHandler
is basically a view, without element and additional generic parameterEvent
. I have decided against this for now, because I think this just means suffering from the orphan rule. I may investigate this again, when it makes sense to integrate this with xilem_core, maybe there are ways, and maybe it really makes sense (in xilem_web this is currently not possible, because of the orphan rule).I mostly fear
impl<State, Action, Context, F> View<State, Action, Context, EventHandlerMessage> for F where F: Fn(&mut State, Event) -> Action
, but maybe it is not as big of an issue as I fear.Drawbacks:
Rust is unfortunately not able to infer the callback of such event handler callback functions.
So instead of
el.on_click(|_, _| {})
it's now necessary to writeel.on_click(|_: &mut _, _| {})
.I think it's not able to infer the lifetime of that first parameter.
I wonder, if there are ways to help rustc infer this without explicit (lifetime) type.