Skip to content

Add useful methods to UseStateHandle & UseReducerHandle#3422

Merged
Madoshakalaka merged 4 commits intoyewstack:masterfrom
its-the-shrimp:add-handle-methods
Apr 4, 2026
Merged

Add useful methods to UseStateHandle & UseReducerHandle#3422
Madoshakalaka merged 4 commits intoyewstack:masterfrom
its-the-shrimp:add-handle-methods

Conversation

@its-the-shrimp
Copy link
Copy Markdown
Contributor

@its-the-shrimp its-the-shrimp commented Sep 28, 2023

Description

This PR adds:

  • UseStateHandle::get & UseReducerHandle::get that return the contained value of the handle in the form in which it's stored internally: in an Rc; These 2 methods will make it easier to work with non(-trivially)-clonable types in said containers;
  • UseStateHandle::into_inner & UseReducerHandle::into_inner that consume the handle and return its 2 main parts: the inner value and the setter/dispatcher associated with the handle. These methods will allow for avoiding cloning in certain contexts where one would have to use get() + setter()/dispatcher() otherwise.

Checklist

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Sep 28, 2023

Visit the preview URL for this PR (updated for commit 987291a):

https://yew-rs--pr3422-add-handle-methods-odfhugyl.web.app

(expires Sat, 11 Apr 2026 10:42:08 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Sep 28, 2023

Benchmark - SSR

Yew Master

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 311.033 311.792 311.235 0.245
Hello World 10 469.941 472.973 471.548 1.025
Function Router 10 32322.319 33783.085 33218.247 496.909
Concurrent Task 10 1006.164 1007.702 1006.980 0.548
Many Providers 10 1099.728 1157.581 1120.331 23.181

Pull Request

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 310.853 311.750 311.241 0.250
Hello World 10 484.220 490.183 486.353 1.618
Function Router 10 33448.716 34433.314 34025.568 353.347
Concurrent Task 10 1006.130 1007.385 1006.646 0.471
Many Providers 10 1060.563 1108.646 1075.848 15.034

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Sep 28, 2023

Size Comparison

Details
examples master (KB) pull request (KB) diff (KB) diff (%)
async_clock 100.817 100.817 0 0.000%
boids 168.471 168.471 0 0.000%
communication_child_to_parent 94.097 94.097 0 0.000%
communication_grandchild_with_grandparent 105.920 105.920 0 0.000%
communication_grandparent_to_grandchild 102.276 102.276 0 0.000%
communication_parent_to_child 91.507 91.507 0 0.000%
contexts 105.979 105.979 0 0.000%
counter 86.818 86.818 0 0.000%
counter_functional 88.854 88.855 +0.001 +0.001%
dyn_create_destroy_apps 90.733 90.733 0 0.000%
file_upload 99.832 99.832 0 0.000%
function_delayed_input 94.813 94.812 -0.001 -0.001%
function_memory_game 173.688 173.688 0 0.000%
function_router 397.049 397.048 -0.001 -0.000%
function_todomvc 164.923 164.924 +0.001 +0.001%
futures 235.551 235.551 0 0.000%
game_of_life 105.053 105.053 0 0.000%
immutable 259.783 259.783 0 0.000%
inner_html 81.341 81.341 0 0.000%
js_callback 109.977 109.979 +0.003 +0.003%
keyed_list 180.429 180.429 0 0.000%
mount_point 84.713 84.713 0 0.000%
nested_list 113.634 113.634 0 0.000%
node_refs 92.085 92.085 0 0.000%
password_strength 1719.265 1719.265 0 0.000%
portals 93.571 93.571 0 0.000%
router 367.937 367.937 0 0.000%
suspense 113.959 113.957 -0.002 -0.002%
timer 88.964 88.964 0 0.000%
timer_functional 99.346 99.433 +0.087 +0.087%
todomvc 142.639 142.639 0 0.000%
two_apps 86.688 86.688 0 0.000%
web_worker_fib 136.449 136.448 -0.001 -0.001%
web_worker_prime 184.835 184.833 -0.002 -0.001%
webgl 83.485 83.485 0 0.000%

✅ None of the examples has changed their size significantly.

/// Returns the inner value of the handle.
pub fn get(&self) -> Rc<T> {
// Safety: `UseStateReducer<T>` is `repr(transparent)` and only contains `T`
unsafe { transmute(self.inner.get()) }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we avoid the transmute somehow?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

not really, this transmute converts between Rc<UseStateReducer<T>> and Rc<T>, there's not much you can do with a value inside an Rc

Copy link
Copy Markdown
Contributor

@kirillsemyonkin kirillsemyonkin Sep 29, 2023

Choose a reason for hiding this comment

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

You would need to convert Rc<UseStateReducer<T>> -> Rc<T> (assuming T is not guaranteed to be Clone) some other way then (maybe via Any?). I think using repr(transparent) was a brilliant idea for Rcs and now I hope Rust makes it like a safe trait / Rc function.

Copy link
Copy Markdown
Member

@futursolo futursolo left a comment

Choose a reason for hiding this comment

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

I am not in favour of this pull request.

We have discussed in some previous pull requests. How a state is stored should be an implementation detail. Not exposing this allows us to switch the underlying implementation at a later time.

e.g.:
To implement concurrent mode, we will need to have multiple schedulers and states need to be associated with a scheduler ID .
I am not exactly settled on how this would be associated.
This pull request rules out the following possibility.

struct UseStateReducer<T> {
    inner: T,
    scheduler_id: Id,
}

@its-the-shrimp
Copy link
Copy Markdown
Contributor Author

The PR does restrict the flexibility in implementation, but on the other hand, how likely is the implementation to change? The current approach works just fine, obtaining/setting the value doesn't require much indirection, and the new methods will allow for easier usage of non(-trivially)-clonable types in the aforementioned handle types.

@kirillsemyonkin
Copy link
Copy Markdown
Contributor

kirillsemyonkin commented Sep 29, 2023

@futursolo Provide other solutions of being able to encapsulate/not clone setter/dispatcher in Yew then.

  1. This PR is best case, since it being tied to implementation helps users to have an easy optimization path and easy access to the current value in a familiar Clone wrapper (which is being used for Reducible by the way).

  2. Medium case is introducing a custom wrapper type UseStateValue<T> instead of giving the Rc<T> to the user. This type may contain your wanted scheduler_id, should be able to be Clone as UseStateHandle is, while not forcing clones of setter/dispatcher upon the users, as well as allowing the developer to not give power of changing parent's state to children.

#[derive(Clone, ...)] // also ImplicitClone
struct UseStateValue<T> {
    inner: Rc<T>, // using same `repr(transparent)`/`transmute` trick
}

impl Deref for UseStateValue<T> {
    type Target = T;
    ...
}
  1. Worst case is adding custom wrapper type UseStateValue<T> that simply wraps UseStateHandle<T>. This type will not contain .set, but it will still be including setter/dispatcher via UseStateHandle<T>. It will also not be allowed in functions as an argument where Rc<T> parameter is required (functions trying to support this will need to have a impl Deref<Target = T> parameter instead) (medium case also suffers from this).

  2. Even worst-er case is not doing anything and making users implement the worst case themselves.

Also possible that use_state will change so much in future that it might not even allow to get a value out of it as easily as Deref (e.g. callback access only). In such case this PR is quite benevolent, since these changes will vanish at that point as well.

@ranile
Copy link
Copy Markdown
Member

ranile commented Sep 30, 2023

The medium case isn't good because oftentimes the value needs to be cloned after getting it out of the state, forcing T to be Clone when it can be avoided. It's either that or recreating the Rc that we just took the value out of.

Yew playground has a component that suffers from this exact same thing today when attaching the change listener to Monaco editor


Concurrent mode, when implemented, will most likely come with breaking changes of its own. I'd rather remove these methods then, providing a suitable alternative, than not add them in the first place

@its-the-shrimp
Copy link
Copy Markdown
Contributor Author

Btw, just as an idea, the fact that those new methods return an Rc does not mean they lock us to one impl, even if we switch to an impl that'll make those structs contain just an interned ID, as long as there's a way to access the interned value, we will be able to provide it in an Rc, either by cloning the Rc if that's how the object will be stored, or by allocating it in an Rc, after which we'd add a warning to the docs of those methods regarding their perf implications

its-the-shrimp and others added 2 commits April 4, 2026 18:54
- Renamed get() to value_rc() on both UseStateHandle and
  UseReducerHandle to avoid shadowing Deref targets that define
  their own get() (e.g. Cell, RefCell, Option).
- Updated UseReducerHandle::value_rc() and into_inner() to work
  with the new current_state: Rc<RefCell<Rc<T>>> internals.
- Reverted the no-longer-needed disambiguation in suspense/hooks.rs.
- Added explicit transmute type annotations to satisfy clippy.
- Updated timer_functional example to showcase into_inner().
- Added docs for value_rc() and into_inner() in en, ja, zh-Hans,
  zh-Hant.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 4, 2026

Benchmark - core

Yew Master

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.154 ns      │ 2.184 ns      │ 2.159 ns      │ 2.161 ns      │ 100     │ 1000000000

Pull Request

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.161 ns      │ 3.438 ns      │ 3.406 ns      │ 2.901 ns      │ 100     │ 1000000000

- The use_state_works test compared value_rc() against Rc::new(0), but
  value_rc() reads the live shared state which advances across
  re-renders. Changed to assert_eq!(*counter.value_rc(), *counter)
  which holds on every render.
- Added hidden # import/component boilerplate to all code snippets in
  state.mdx (en, ja, zh-Hans, zh-Hant) so the website-test doctest
  harness can compile them.
@Madoshakalaka Madoshakalaka reopened this Apr 4, 2026
@Madoshakalaka Madoshakalaka added the A-yew Area: The main yew crate label Apr 4, 2026
@Madoshakalaka
Copy link
Copy Markdown
Member

Madoshakalaka commented Apr 4, 2026

Thanks for the PR @its-the-shrimp, and thanks to @ranile, @futursolo, and @kirillsemyonkin for the discussion.

I've taken over this PR to rebase it onto current master and address the feedback. Here's what changed:

Renamed get() to value_rc()

The original get() method creates a semver hazard: adding an inherent get() on UseStateHandle<T> shadows any T::get() that users reach via Deref. Common types like Cell, RefCell, and Option all have .get(), so this would silently break method resolution for existing code. The PR itself had to patch suspense/hooks.rs for exactly this reason. Renaming to value_rc() avoids the conflict entirely, and the suspense patch is no longer needed.

Adapted to new UseReducerHandle internals

Since this PR was opened, UseReducerHandle was reworked (the value: Rc<T> field was replaced with current_state: Rc<RefCell<Rc<T>>> plus a deref_history). Both value_rc() and into_inner() have been updated accordingly.

Addressing @futursolo's concurrent mode concern

It's been over two years since the discussion about concurrent mode potentially requiring a scheduler_id in the state struct. That work hasn't materialized, and as @ranile pointed out, concurrent mode will bring its own breaking changes regardless. As @its-the-shrimp noted, returning Rc<T> doesn't actually lock us into any particular internal representation: even if the storage changes, we can always provide an Rc<T> (via clone or allocation) and document performance implications.

Additional changes

  • Updated the timer_functional example to use into_inner().
  • Added documentation for both methods to the website docs
  • Added explicit transmute type annotations to satisfy clippy

Copy link
Copy Markdown
Member

@Madoshakalaka Madoshakalaka left a comment

Choose a reason for hiding this comment

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

merging

@Madoshakalaka Madoshakalaka merged commit 686f08e into yewstack:master Apr 4, 2026
37 checks passed
shan-shaji pushed a commit to shan-shaji/yew that referenced this pull request Apr 19, 2026
…eHandle` & `UseReducerHandle` (yewstack#3422)

* added (UseReducerHandle/UseStateHandle)::(value_rc/into_inner)

* add docs

* Updated timer_functional example to showcase into_inner().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-yew Area: The main yew crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants