Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/yew/src/functional/hooks/use_reducer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,12 @@ where
let should_render_fn = should_render_fn.clone();
let mut val = val.borrow_mut();
let next_val = (*val).clone().reduce(action);
let should_render = should_render_fn(&next_val, &val);

// Check if the reduce action just returned the same `Rc` again
// instead of producing a new one.
let rc_was_reused = Rc::ptr_eq(&val, &next_val);

let should_render = !rc_was_reused && should_render_fn(&next_val, &val);
Copy link
Member

@WorldSEnder WorldSEnder Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use_reducer currently makes the explicit promise that "This hook will always trigger a re-render upon receiving an action". Please change the docs, as that's a breaking change. If you are reading this from the future and relying on this behaviour, use_force_update provides a hook to trigger this rerender manually.

Since use_reducer_base is internal, could you push the changes to the callers into their respective should_render_fn for clarity in use_reducer and use_reducer_eq below?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, I updated the documentation accordingly.

As for moving the check into should_render_fn, is the current version along the lines of what you meant? Now the base fn is completely unchanged from what it was before, the diff is only in the should_render_fns

*val = next_val;

should_render
Expand Down
91 changes: 91 additions & 0 deletions packages/yew/tests/use_reducer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,94 @@ async fn use_reducer_eq_works() {
let result = obtain_result();
assert_eq!(result.as_str(), "3");
}

enum SometimesChangeAction {
/// If this action is sent, the state will remain the same
Keep,
/// If this action is sent, the state will change
Change,
}

/// A state that does not implement PartialEq
#[derive(Clone)]
struct SometimesChangingState {
value: i32,
}

impl Reducible for SometimesChangingState {
type Action = SometimesChangeAction;

fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
use SometimesChangeAction::*;
match action {
Keep => self,
Change => {
let mut self_: Self = (*self).clone();
self_.value += 1;
self_.into()
}
}
}
}

#[wasm_bindgen_test]
async fn use_reducer_does_not_rerender_when_rc_is_reused() {
#[component(UseReducerComponent)]
fn use_reducer_comp() -> Html {
let state = use_reducer(|| SometimesChangingState { value: 0 });
let render_count = use_mut_ref(|| 0);

let render_count = {
let mut render_count = render_count.borrow_mut();
*render_count += 1;

*render_count
};

let keep_state = {
let state = state.clone();
Callback::from(move |_| state.dispatch(SometimesChangeAction::Keep))
};

let change_state = Callback::from(move |_| state.dispatch(SometimesChangeAction::Change));

html! {
<>
<div>
{"This component has been rendered: "}<span id="result">{render_count}</span>{" Time(s)."}
</div>
<button onclick={keep_state} id="keep-state">{"Keep State"}</button>
<button onclick={change_state} id="change-state">{"Change State"}</button>
</>
}
}

yew::Renderer::<UseReducerComponent>::with_root(
document().get_element_by_id("output").unwrap(),
)
.render();
sleep(Duration::ZERO).await;

let result = obtain_result();
assert_eq!(result.as_str(), "1");

document()
.get_element_by_id("change-state")
.unwrap()
.unchecked_into::<HtmlElement>()
.click();
sleep(Duration::ZERO).await;

let result = obtain_result();
assert_eq!(result.as_str(), "2");

document()
.get_element_by_id("keep-state")
.unwrap()
.unchecked_into::<HtmlElement>()
.click();
sleep(Duration::ZERO).await;

let result = obtain_result();
assert_eq!(result.as_str(), "2");
}
Loading