Skip to content

Breaking change in 0.22.1: UseStateHandle as a prop no longer triggers child re-renders #4058

@sunjay

Description

@sunjay

Problem

In Yew 0.22.1, passing a UseStateHandle<T> as a prop to a child component no longer triggers re-renders when the state is updated. This is caused by #3988 cc @Madoshakalaka (Very cool feature, just unfortunate that it breaks this for me.)

That PR changed the behavior of UseStateHandle so that dereferencing it always fetches the latest state. While this successfully fixes stale closures, it breaks the PartialEq implementation for UseStateHandle.

(Even if this is eventually the intended behavior, this is a breaking change in a patch release. The example code below works in 0.22.0 but breaks in 0.22.1.)

When a parent component re-renders and passes a new handle to the child, Yew compares the props: **old_handle == **new_handle. Because both handles now fetch the live state, this effectively evaluates to new_state == new_state. This always returns true, causing Yew to incorrectly assume the props haven't changed and skip rendering the child component.

I acknowledge that passing the handle directly like this isn't always recommended, but it has been supported by yew for a long time and this breaks that behavior.

Steps To Reproduce

Steps to reproduce the behavior:

  1. Create a parent component that initializes state with use_state.
  2. Pass that UseStateHandle as a prop to a child component.
  3. Update the state (either from the parent or the child).
  4. Observe that the parent re-renders, but the child component's UI remains frozen because it skips the render step.

Here is a minimal reproducible example:

use yew::prelude::*;

#[derive(Properties, PartialEq)]
pub struct ChildProps {
    pub handle: UseStateHandle<i32>,
}

#[component]
pub fn Child(props: &ChildProps) -> Html {
    let onclick = {
        let handle = props.handle.clone();
        Callback::from(move |_| {
            // In 0.22.1, this correctly updates the state, but the child UI won't update
            handle.set(*handle + 1);
        })
    };

    html! {
        <div style="padding: 10px; border: 1px solid red;">
            <p>{ "Child sees: " }{ *props.handle }</p>
            <button {onclick}>{ "Increment" }</button>
        </div>
    }
}

#[component]
pub fn App() -> Html {
    let state = use_state(|| 0);

    html! {
        <div style="padding: 10px; border: 1px solid blue;">
            <p>{ "Parent sees: " }{ *state }</p>
            <Child handle={state} />
        </div>
    }
}

Expected behavior

When the state is updated, PartialEq for UseStateHandle should correctly evaluate to false so the child component re-renders and displays the updated state, matching the behavior of Yew 0.22.0.

Screenshots

N/A - The UI simply fails to update the child component's DOM.

Environment:

  • Yew version: 0.22.1
  • Rust version: 1.93.1
  • Target, if relevant: wasm32-unknown-unknown
  • Build tool, if relevant: trunk

Questionnaire

  • I'm interested in fixing this myself but don't know where to start
  • I would like to fix and I have a solution
  • I don't have time to fix this right now, but maybe later

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions