Skip to content
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

Re-render in async callback #911

Closed
Kelerchian opened this issue Feb 1, 2020 · 5 comments
Closed

Re-render in async callback #911

Kelerchian opened this issue Feb 1, 2020 · 5 comments
Labels

Comments

@Kelerchian
Copy link

Kelerchian commented Feb 1, 2020

Hi @deniskolodin

I saw this issue #364 and I thought:
Is there any plan to create async callback which in it, component view() can be triggered multiple time

// the Model struct

struct Model {
    link: ComponentLink<Self>,
    pub state: Arc<Mutex<SomeStruct>>
}


// inside fn view()

let state_clone = Arc::clone(&self.state);
let onclick = self.link.async_callback(async move |scope| => {

    // My example of is currently ugly. I think I can do better with macro here
    if let Ok(mut state) = state_clone.lock() {
        (*state).isDoingSomeAsyncThing = true;
    }
    scope.try_refresh().await   // refresh here, imaginarily take current snapshot of Model and queue VNode diffs with the snapshot behind the scene


    // Do some async thing here ...


    if let Ok(mut state) = state_clone.lock() {
        (*state).isDoingSomeAsyncThing = false;
    }
    scope.try_refresh()  // refresh here
})

html! {
    // Render your model here
    <button onclick=onclick>{
        match self.data.lock() {
            Ok(x) => match (*x).isDoingSomeAsyncThing {
                true => "Doing async thing",
                false => "Do async thing",
            },
            _ => "..."
        }

    }</button>
}

This is similar to what we can do in the js world and it'll be an interesting addition to yew

async () => {
  if(this.isRunning) return;
  this.setState({ isDoingSomeAyncThing: true })
  // do async things
  this.setState({ isDoingSomeAyncThing: false })
}

Btw, I'm new to requesting features in OSS projects so let me know if it's not the good way to go

Cheers

@Kelerchian Kelerchian added the feature-request A feature request label Feb 1, 2020
@jstarry jstarry added question and removed feature-request A feature request labels Feb 2, 2020
@jstarry
Copy link
Member

jstarry commented Feb 2, 2020

Hey @Kelerchian thanks for the question!

This is already possible :)

The Yew way of calling this.setState(...) is by sending a message to the component using a ComponentLink. Then your component can choose whether to re-render based on that message by returning true in its update method. See here: https://yew.rs/docs/concepts/components#update

The key point is that ComponentLink can be freely cloned and passed around. In your example, you should clone the link into your async function. Take a look at this example to see how it can be done:
https://github.com/yewstack/yew/blob/master/examples/futures/src/lib.rs#L16

@jstarry jstarry closed this as completed Feb 2, 2020
@jstarry jstarry reopened this Feb 2, 2020
@jstarry
Copy link
Member

jstarry commented Feb 2, 2020

Sorry didn't mean to close, let me know if this helps :)

@Kelerchian
Copy link
Author

Many thanks, @jstarry. That explains the stuff and reminds me that yew's and react's paradigm and technical challenge are not exactly the same. I'm gonna close the issue.

I'm curious, though, is this design intended or caused by some technical difficulties (like mutable reference are only allowed in certain lifecycles)?

Also, I just saw there is this issue #314 and I thought yew will need the type of message React is telling to its user https://reactjs.org/docs/thinking-in-react.html

Notes on elm/redux-like reducer pattern:
I've seen that reducer pattern sometimes leads to pattern issues, such as developers tend to put validations inside the reducer and the reducers are getting stuffed with multiple business logic codes that are not related to each other inside the same method and that kind of pattern doesn't look good on complex components.
I personally think that action name and action payload are better conveyed as fnName and fnParams rather than Message type and Message data.
Last is that I personally think that ShouldRender directive should be a method call and not a return true

But messaging is important for inter-component communication, so this send_message param is a actually a very helpful thing.

@jstarry
Copy link
Member

jstarry commented Feb 2, 2020

I'm curious, though, is this design intended or caused by some technical difficulties (like mutable reference are only allowed in certain lifecycles)?

Design is intended. Denis originally designed Yew such that each component acts as its own actor and updates only on messages to keep logic (hopefully) straightforward.

Also, I just saw there is this issue #314 and I thought yew will need the type of message React is telling to its user https://reactjs.org/docs/thinking-in-react.html

Totally agree. We do have https://yew.rs but it still needs some work

I personally think that action name and action payload are better conveyed as fnName and fnParams rather than Message type and Message data.

Interesting, kinda like RPC style? I think the messaging naming goes well with the actor design but I can definitely see where you're coming from!

Last is that I personally think that ShouldRender directive should be a method call and not a return true

This could work. We would need Yew to debounce the render requests. Could you expand on why you think a method call would be better than the current approach? One thing that's nice about the return is that it limits the update call to only calling render at most one time.

@Kelerchian
Copy link
Author

Interesting, kinda like RPC style? I think the messaging naming goes well with the actor design but I can definitely see where you're coming from!

Only for in-component interactions. For example:
self.rerender(), self.activate()

The reason is that it's unintuitive to 'send message to self' to trigger some rerender. It'll also be manageable since function call are encouraged to only be used as private

For inter-component interactions, Current, way is the way. C# event/observable syntax is nice too but it seems to be easier to do in rust. Syntax wise it's shouldn't necessarily the same.

fn mount(){
   // This is where we instruct the component to listen to event
   // java like singleton
   // kinda need delisten call somewhere 
   some_module::get_single_instance().listen(|event| {
      match event {
         Msg::Activate => self.activate(),
         Msg::DoIt => self.receiveMessage(event)
      }
   })
}

fn receiveMessage(context: &mut Context<Msg>, model: &mut Model, msg: Msg) {
    match msg {
        Msg::DoIt => {
            // This is where we handle the event
            self.doSomething()
        }
    }
    // Imaginary update-like function where it doesn't have to return bool
}

This could work. We would need Yew to debounce the render requests. Could you expand on why you think a method call would be better than the current approach? One thing that's nice about the return is that it limits the update call to only calling render at most one time.

Ah I see that debounce might make things comples.
Method call for refresh would be a better than the current approach and why I prefer that over fn that must returns true because:
1.) It's explicit for the yew consumer. I think yew user should not be forced to care what's under the hood. You want refresh, you call refresh.
2.) Syntax wise it's easier to read. I wish we could write just like below, focusing on business logic and not having to struggle with syntax.

let onclick = || {
   // borrow mutable comp reference
   // queue the callback and execute them
   self.link.borrowComponent(|&mut comp| { 
     future_to_promise(async move {
       comp.isRefreshing = true;
       comp.render();
       let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
       comp.data = resp_value;
       comp.isRefreshing = false;
       comp.render();
     });
   });
}

3.) I always had difficulties navigating between files in redux just to understand the business logic, that's where the "prefer-not-to" attitude came from, lol. (But actually redux's flaw is not the message pattern but it's tendency to drive more junior devs to write god object)
4.) I never thought any devs would intentionally call render multiple times. Rendering multiple times after state changes should be easier if these kind of pattern are allowed in yew.

But I don't know yet about any technical challenges that you guys might face just to allow this kind of programming pattern. I wish I know more what's under yew's hood and help with it. I think I'll be skimming through the examples to understand more of yew.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants