Skip to content

Conversation

@Pascal-So
Copy link

Description

Yew currently provides two versions of the reducer hook:

  • use_reducer: This hook always causes a re-render when an action is dispatched.
  • use_reducer_eq: This hook avoids the re-render if the new state is the same as the old state. However, the state now has to implement PartialEq.

I feel like there is room for a middle ground: The reducer takes an Rc and returns an Rc. Even if we don't have a PartialEq implementation on the state, we can still know that the state must be unchanged if the reducer returns the exact same Rc back to us. This check is almost free (just a pointer comparison).

This PR adds a simple check if the two Rcs are the same, and in that case skip the re-render.

Note that even for use_reducer_eq this check is beneficial, since the PartialEq implementation might be more expensive than just a pointer comparison.

This change might be a breaking change if someone was relying on the exact number of re-renders, but I'd assume that this is not covered in Yew's semver guarantees, right?

Checklist

  • I have reviewed my own code
  • I have added tests

@github-actions
Copy link

github-actions bot commented Nov 16, 2025

Visit the preview URL for this PR (updated for commit 5e3dadf):

https://yew-rs-api--pr3945-use-reducer-optimiza-u4xs77n3.web.app

(expires Tue, 25 Nov 2025 22:37:31 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

@github-actions
Copy link

github-actions bot commented Nov 16, 2025

Benchmark - core

Yew Master

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.67 ns       │ 3.139 ns      │ 2.675 ns      │ 2.684 ns      │ 100     │ 1000000000

Pull Request

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.712 ns      │ 3.025 ns      │ 2.717 ns      │ 2.721 ns      │ 100     │ 1000000000

@github-actions
Copy link

github-actions bot commented Nov 16, 2025

Benchmark - SSR

Yew Master

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 270.214 272.125 270.561 0.565
Hello World 10 470.772 482.678 478.514 3.807
Function Router 10 1550.812 1586.253 1568.509 10.931
Concurrent Task 10 1005.011 1006.697 1005.780 0.588
Many Providers 10 1031.203 1065.500 1049.301 11.537

Pull Request

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 281.405 281.765 281.542 0.116
Hello World 10 477.791 496.804 481.093 5.609
Function Router 10 1602.130 1617.413 1607.869 3.923
Concurrent Task 10 1004.972 1007.096 1006.098 0.810
Many Providers 10 1057.383 1115.522 1079.133 15.417

@github-actions
Copy link

github-actions bot commented Nov 16, 2025

Size Comparison

Details
examples master (KB) pull request (KB) diff (KB) diff (%)
async_clock 98.312 98.312 0 0.000%
boids 166.434 166.434 0 0.000%
communication_child_to_parent 91.440 91.440 0 0.000%
communication_grandchild_with_grandparent 102.572 102.572 0 0.000%
communication_grandparent_to_grandchild 98.954 98.954 0 0.000%
communication_parent_to_child 88.811 88.811 0 0.000%
contexts 103.940 103.943 +0.003 +0.003%
counter 85.489 85.489 0 0.000%
counter_functional 85.756 85.759 +0.003 +0.003%
dyn_create_destroy_apps 88.647 88.647 0 0.000%
file_upload 97.972 97.972 0 0.000%
function_delayed_input 91.063 91.066 +0.003 +0.003%
function_memory_game 169.376 169.381 +0.005 +0.003%
function_router 328.280 328.325 +0.045 +0.014%
function_todomvc 161.585 161.599 +0.014 +0.008%
futures 236.928 236.928 0 0.000%
game_of_life 103.849 103.849 0 0.000%
immutable 192.977 192.979 +0.003 +0.002%
inner_html 79.966 79.966 0 0.000%
js_callback 107.514 107.517 +0.003 +0.003%
keyed_list 179.134 179.134 0 0.000%
mount_point 83.186 83.186 0 0.000%
nested_list 112.699 112.699 0 0.000%
node_refs 90.833 90.833 0 0.000%
password_strength 1743.419 1743.419 0 0.000%
portals 92.307 92.307 0 0.000%
router 301.961 301.967 +0.006 +0.002%
suspense 111.686 111.688 +0.003 +0.003%
timer 88.241 88.241 0 0.000%
timer_functional 95.934 95.938 +0.005 +0.005%
todomvc 141.684 141.684 0 0.000%
two_apps 85.341 85.341 0 0.000%
web_worker_fib 132.882 132.896 +0.014 +0.010%
web_worker_prime 184.158 184.185 +0.026 +0.014%
webgl 82.524 82.524 0 0.000%

✅ None of the examples has changed their size significantly.

// 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants