Open ghost opened 9 months ago
I think the question boils down to "how can I use iternally mutable data with Reducible
". I'll try to answer this, but first I want to clarify why the behaviour of Implementation 2 is expected.
First of all, use_reducer
always leads to a re-render when you dispatch an action to it, no matter what the implementation of Reducible
does. This is what you experience by the context providing component always re-rendering. The difference between the two examples is how the ContextProvider
handles the value in its context
; to decide whether to re-render any subscribed children, it compares the old to the new value when it itself re-renders. This means we should look at the PartialEq
for state: UseReducerHandle<_>
, which simply forwards to <StateData as PartialEq>::eq
. But here lies the problem: if you use only interior mutability in the reducer, then the old value will have been also updated, and the comparison is a trivially true by reflexivity! So the context provider concludes that it doesn't need to re-render its children.
So how to fix it? The simplest, but perhaps too lazy of an approach, would be to wrap the context value in a struct that simply always returns false from its PartialEq
. This way, the ContextProvider
will always re-render children consumers, whenever it itself re-renders.
struct ChatroomContext(UseReducerHandle<StateData>);
impl PartialEq for ChatroomContext {
fn eq(&self, other: &Self) { false }
}
...
<ContextProvider<ChatroomContext> context={ChatroomContext(state)} >
...
In case this is too coarse and leads to too many unnecessary re-renders, you should introduce some sort of generational counter into the state that lives outside the shared mutable state that counts which "version" the state represents:
pub struct SharedState {
pub open: bool, // etc..
}
#[derive(Clone)]
pub struct StateData {
generation: u32,
pub state: Rc<RefCell<SharedState>>,
}
impl PartialEq for StateData {
fn eq(&self, other: &Self) -> bool {
// this assumes that there is only one history for each state, which is true in use_reducer
Rc::ptr_eq(&self.state, &other.state) && self.generation == other.generation
}
}
impl StateData {
// small helper method to facilitate incrementing the generation counter
fn mutate<'a>(self: &'a mut Rc<Self>) -> impl 'a + DerefMut<Target = SharedState> {
// We can cheaply clone ourself. With some further work you can write this without
// leaking the `Clone` impl for StateData
let this = Rc::make_mut(self);
// Since we are borrowing mutably, we assume that the data "changed" and we increase the generation
this.generation += 1;
this.state.borrow_mut()
}
}
impl Reducible for StateData {
type Action = StateAction;
fn reduce(mut self: Rc<Self>, action: Self::Action) -> Rc<Self> {
match action {
StateAction::SetPreviewOpen(x) => {
let mut state = StateData::mutate(&mut self);
state.open = x;
}
}
self
}
}
....
// This context provider will see the same shared state, but different generation numbers
// On subsequent re-renders, it will trigger re-renders of downstream when the generation changed
<ContextProvider<ChatroomContext> context={state} >
....
Problem
Thank you for taking the time to read my issue.
I'm not sure if this is my implementation error or a yew bug but I'll try to explain the situation as clear as possible.
I am developing an application where I need a context that requires some fields that are of type
Vec<T>
so for sake of not copying data around I wrap each struct member in Rc<RefCell<>>.there are two implementations of Reducible trait that each behave differently.
First implementation: Create a new Self. Causes the context subscribers and the context itself to reload when I click either button which is expected behavior.
Second implementation: Change underlying data and return self. Causes only the context itself to reload on click and not the subscribers even if underlying data is changed, there for the UI does not update which is not what I want.
How should I approach this problem since I cannot create a new Self on each state update ?
Steps To Reproduce Code:
Environment:
stable
]wasm32-unknown-unknown
]trunk
]Windows 11
]Brave 1.62.162 Chromium: 121.0.6167.164 (Official Build) (64-bit)
]Questionnaire