Skip to content
This repository has been archived by the owner on Mar 2, 2021. It is now read-only.

Ownership of RootRender #93

Open
richard-uk1 opened this issue Jul 7, 2019 · 1 comment
Open

Ownership of RootRender #93

richard-uk1 opened this issue Jul 7, 2019 · 1 comment

Comments

@richard-uk1
Copy link

richard-uk1 commented Jul 7, 2019

When you want to respond to a user event, you use the following builder method

pub fn on<F>(self, event: &'a str, callback: F) -> Self where
    F: 'static + Fn(&mut dyn RootRender, VdomWeak, Event), 

The problem comes if you want to then pass the &mut dyn RootRender to a future, for example if you want to update state based on some asynchronous event like an api call. Because futures must be 'static, there's no way to do it.

A potential solution would be for that field to give you something like Rc<Box<dyn RootRender>>, thereby giving you ownership, so you can use it at any point in the future.

What do you think about this?

EDIT I guess it would need to be Rc<RefCell<dyn RootRender>> so you could mutate the state.

@extraymond
Copy link

Hi! I was experimenting with the idea to trigger async calls too.
After the advice #99 from @fitzgen that utilize mpsc to trigger mutation, I end up with the following pattern.

struct Data {
    value: i32,
}

enum Msg {
    Add,
    Minus,
}

struct Entity {
    tx: mpsc::UnboundedSender<Msg>,
    data: Rc<Mutex<Data>>,
}

fn main() {
    let (tx, rx) = mpsc::unbounded::<Msg>();
    let data = Rc::new(Mutex::new(Data { value: 0 }));
    let entity = Entity { data, tx };
    let vdom = Vdom::new(&body, entity));

    let data_handle = data.clone();
    let rendering = async move {
        while let Some(msg) = rx.next().await {
            let data = data_clone.lock().await;
            match msg {
                Msg::Add => {
                    data.value +=1;
                }
                Msg::Minus => {
                    data.value -=1;
                }
            }
            vdom.weak().render().compat().await.unwrap();
        }
    };
    spawn_local(rendering)
}
  1. create a struct that contains the data field and a pair of mpsc::channel
  2. obtain the vdom object without forget it.
  3. create the struct and create a async task that listens to the Receiver of the pair.
  4. On any new incoming message, obtain the data field and mutate it accordingly.
  5. Trigger render manually.

Since tx is cloneable, you can then move the sender on your event handler and eventually on your async task.

impl Render for Entity {

    fn render<'a>(&self, ctx: &mut RenderContext<'a>) -> Node<'a> {
        let tx = self.tx.clone();
        let closure = move |_, _, event| {
            let tx = tx.clone();
            let fut = async move {
                tx.send(Msg::Add).await.unwrap();
            };

            spawn_local(fut);
        };

        // mount the closure on your view.
    }
}

Therefore you can send msg that trigger mutation sometimes later.
Another benefit or separating rendering loop and the mutation is that you can have multiple entities that might be nested into each other.

Previously if we want to do mutation in the closure, we would have to use RootRender which would only points to the one mounted to the vdom. By decoupling data mutation and RootRender, if nested components is presents, we can always mutate on self instead of traversing from RootRender to the current component.

I've create a helper-crate to record stuff on my journey of learning to use dodrio(and typed-html), you can find a more detailed version in that repo.
https://github.com/extraymond/dodrio-ext

I'm not sure if this is optimal though. Just finding it ergonomic to play with dodrio.
Since I'm still a rookie in rust/wasm, if you find some anti-pattern or better ways to achieve this, I would love to hear advise from you.

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

No branches or pull requests

2 participants