From a0406ae72760408a46a81d16c761691acb60507c Mon Sep 17 00:00:00 2001 From: Pascal Sommer
Date: Sun, 16 Nov 2025 14:23:46 +0100
Subject: [PATCH 1/5] add failing test
---
packages/yew/tests/use_reducer.rs | 91 +++++++++++++++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/packages/yew/tests/use_reducer.rs b/packages/yew/tests/use_reducer.rs
index 2394f49a13c..53191c024a6 100644
--- a/packages/yew/tests/use_reducer.rs
+++ b/packages/yew/tests/use_reducer.rs
@@ -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
Date: Sun, 16 Nov 2025 14:27:22 +0100
Subject: [PATCH 2/5] avoid rerender in use_reducer when Rc is reused
---
packages/yew/src/functional/hooks/use_reducer.rs | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/yew/src/functional/hooks/use_reducer.rs b/packages/yew/src/functional/hooks/use_reducer.rs
index db81c38179d..23a41608460 100644
--- a/packages/yew/src/functional/hooks/use_reducer.rs
+++ b/packages/yew/src/functional/hooks/use_reducer.rs
@@ -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);
*val = next_val;
should_render
From cff516e499719883e22e578b1c9b31e9f7276358 Mon Sep 17 00:00:00 2001
From: Pascal Sommer
Date: Tue, 18 Nov 2025 21:47:23 +0100
Subject: [PATCH 3/5] add warning comment about assumptions
---
packages/yew/src/functional/hooks/use_reducer.rs | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/packages/yew/src/functional/hooks/use_reducer.rs b/packages/yew/src/functional/hooks/use_reducer.rs
index 23a41608460..9611205094a 100644
--- a/packages/yew/src/functional/hooks/use_reducer.rs
+++ b/packages/yew/src/functional/hooks/use_reducer.rs
@@ -225,8 +225,14 @@ where
let mut val = val.borrow_mut();
let next_val = (*val).clone().reduce(action);
- // Check if the reduce action just returned the same `Rc` again
- // instead of producing a new one.
+ // Check if the reduce action just returned the same `Rc` again instead of producing
+ // a new one.
+ // NOTE: here we make the assumption that an unchanged address implies that the
+ // "identity" of the `Rc` is unchanged. This assumption is valid here because we
+ // still keep the old Rc around. But if we were to instead move the old Rc into
+ // the `reduce` function, then the address could be reused and the object inside
+ // the Rc might be different. The `rc_was_reused` variable is thus only meaningful
+ // as long as we use a `clone` before `reduce`.
let rc_was_reused = Rc::ptr_eq(&val, &next_val);
let should_render = !rc_was_reused && should_render_fn(&next_val, &val);
From 11a464021339e1f8a7def1f6f000ad5a327ccdfe Mon Sep 17 00:00:00 2001
From: Pascal Sommer
Date: Tue, 18 Nov 2025 21:58:53 +0100
Subject: [PATCH 4/5] update use_reducer docs
---
packages/yew/src/functional/hooks/use_reducer.rs | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/packages/yew/src/functional/hooks/use_reducer.rs b/packages/yew/src/functional/hooks/use_reducer.rs
index 9611205094a..6368cc4f622 100644
--- a/packages/yew/src/functional/hooks/use_reducer.rs
+++ b/packages/yew/src/functional/hooks/use_reducer.rs
@@ -275,8 +275,13 @@ where
/// implement a `Reducible` trait which defines the associated `Action` type and a
/// reducer function.
///
-/// This hook will always trigger a re-render upon receiving an action. See
-/// [`use_reducer_eq`] if you want the component to only re-render when the state changes.
+/// This hook will trigger a re-render whenever the reducer function produces a new `Rc` value upon
+/// receiving an action. If the reducer function simply returns the original `Rc` then the component
+/// will not re-render. See [`use_reducer_eq`] if you want the component to first compare the old and
+/// new state and only re-render when the state actually changes.
+///
+/// To cause a re-render even if the reducer function returns the same `Rc`, take a look at
+/// [`use_force_update`].
///
/// # Example
/// ```rust
From 5e3dadf23ec0190688b2aeb788aadf91a6f9dee3 Mon Sep 17 00:00:00 2001
From: Pascal Sommer
Date: Tue, 18 Nov 2025 23:33:13 +0100
Subject: [PATCH 5/5] move check to should_render_fn
---
.../yew/src/functional/hooks/use_reducer.rs | 26 +++++++------------
1 file changed, 10 insertions(+), 16 deletions(-)
diff --git a/packages/yew/src/functional/hooks/use_reducer.rs b/packages/yew/src/functional/hooks/use_reducer.rs
index 6368cc4f622..fbc819fb5e7 100644
--- a/packages/yew/src/functional/hooks/use_reducer.rs
+++ b/packages/yew/src/functional/hooks/use_reducer.rs
@@ -224,18 +224,7 @@ where
let should_render_fn = should_render_fn.clone();
let mut val = val.borrow_mut();
let next_val = (*val).clone().reduce(action);
-
- // Check if the reduce action just returned the same `Rc` again instead of producing
- // a new one.
- // NOTE: here we make the assumption that an unchanged address implies that the
- // "identity" of the `Rc` is unchanged. This assumption is valid here because we
- // still keep the old Rc around. But if we were to instead move the old Rc into
- // the `reduce` function, then the address could be reused and the object inside
- // the Rc might be different. The `rc_was_reused` variable is thus only meaningful
- // as long as we use a `clone` before `reduce`.
- let rc_was_reused = Rc::ptr_eq(&val, &next_val);
-
- let should_render = !rc_was_reused && should_render_fn(&next_val, &val);
+ let should_render = should_render_fn(&next_val, &val);
*val = next_val;
should_render
@@ -277,8 +266,8 @@ where
///
/// This hook will trigger a re-render whenever the reducer function produces a new `Rc` value upon
/// receiving an action. If the reducer function simply returns the original `Rc` then the component
-/// will not re-render. See [`use_reducer_eq`] if you want the component to first compare the old and
-/// new state and only re-render when the state actually changes.
+/// will not re-render. See [`use_reducer_eq`] if you want the component to first compare the old
+/// and new state and only re-render when the state actually changes.
///
/// To cause a re-render even if the reducer function returns the same `Rc`, take a look at
/// [`use_force_update`].
@@ -366,7 +355,7 @@ where
T: Reducible + 'static,
F: FnOnce() -> T,
{
- use_reducer_base(init_fn, |_, _| true)
+ use_reducer_base(init_fn, |a, b| !address_eq(a, b))
}
/// [`use_reducer`] but only re-renders when `prev_state != next_state`.
@@ -379,5 +368,10 @@ where
T: Reducible + PartialEq + 'static,
F: FnOnce() -> T,
{
- use_reducer_base(init_fn, T::ne)
+ use_reducer_base(init_fn, |a, b| !address_eq(a, b) && a != b)
+}
+
+/// Check if two references point to the same address.
+fn address_eq