rustyew

How to share a HashMap state amongst Yew Rust components?


I want to use a State hook wrapped around a HashMap to share the locations of Yew components. This is so that when each component is drawn and its position is set, another component will be notified (to draw lines between the components).

This is a broken example.

The idea is that on_ref_update will be called each time a component's position changes. This will set values in the HashMap, and this will be used to notify a listener (the connections component). I have left out the components with positions for brevity.

From what I have read this is the typical way to do it, but I get the compiler errors:

Type mismatch [E0308] expected HashMap<String, Element>, but found UseStateHandle<HashMap<String, Element>> Cannot move

But as I understand from another question (How to get the value from state in Yew Rust) I should just dereference other_node_positions to get the state's value.

#[function_component(Pane)]
pub fn pane(PaneProps { some_prop }: &PaneProps) -> Html {
    let other_node_positions: UseStateHandle<HashMap<String, Element>> = use_state(|| HashMap::new());

    let on_ref_update = {
        let mut other_node_positions =  &other_node_positions;
        Callback::from(move |(id, element): (String, Element)| {
            other_node_positions.set({
                other_node_positions.insert(id, element);
                *other_node_positions
            });
        })
    };
    

    html! {
        <div>
            <Connections node_positions={Rc::clone(&other_node_positions)} />
        </div>
    }
}

Solution

  • There are several issues with your example:

    1. Your state is of type HashMap<_, _> but you want to pass it as a Rc<HashMap<_, _>> to a child component via Rc::clone(&_).
    2. You pass &other_node_positions, which is borrowed data, to a Callback that may outlive other_node_positions.
    3. You attempt to move out of a UseStateHandle containing a non-Copy type via *other_node_positions.

    Here is one way to solve these issues:

    #[function_component(Pane)]
    pub fn pane() -> Html {
        // Fix #1 by adding `Rc<_>` to state type.
        let other_node_positions: UseStateHandle<Rc<HashMap<String, Element>>> = use_state(|| Rc::new(HashMap::new()));
    
        let _on_ref_update = {
            // Fix #2 by cloning the `UseStateHandle`.
            let other_node_positions =  other_node_positions.clone();
            Callback::from(move |(id, element): (String, Element)| {
                // Fix #3 by cloning the `HashMap<_, _>` inside the `Rc<_>` state.
                let mut map = (**other_node_positions).clone();
                map.insert(id, element);
                other_node_positions.set(Rc::new(map));
            })
        };
    
        html! {
            <div>
                <Connections node_positions={Rc::clone(&other_node_positions)} />
            </div>
        }
    }