Context with Rc<Refcell<T>> reducer type has unexpected behavior

Open
#3,601 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
38/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Stale
Tech stack
rust, wasm
Domain
frontend, web-dev

Research direction

Start at the Reducible::reduce implementation and trace how use_reducer and ContextProvider determine whether context subscribers rerender. Reproduce the two StateData implementations from the issue on the wasm32-unknown-unknown target, then inspect existing reducer or context tests if present. Done means the behavior is explained and the subscriber UI updates correctly for the intended state change.

Written by the indexing model from the issue text.

Description

A-yew documentation ergonomics question

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.
image

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.
image

How should I approach this problem since I cannot create a new Self on each state update ?

Steps To Reproduce
Code:

use yew::prelude::*;
use gloo::console::log;
use std::rc::Rc;
use std::cell::RefCell;


#[derive(PartialEq)]
pub struct StateData {
    pub preview_open: Rc<RefCell<bool>>,
    // some other vector data that I dont want to copy on each state change
}

impl Default for StateData {
    fn default() -> Self {
        Self {
            preview_open: Rc::new(RefCell::new(false)),
        }
    }
}

pub enum StateAction {
    SetPreviewOpen(bool),
}

// First implementation
impl Reducible for StateData {
    type Action = StateAction;

    fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
        match action {
            StateAction::SetPreviewOpen(x) => {
                return Self {
                    preview_open: Rc::new(RefCell::new(x)),
                }.into();
            }
        }
    }
}

// Second implementation
// impl Reducible for StateData {
//     type Action = StateAction;
// 
//     fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
//         match action {
//             StateAction::SetPreviewOpen(x) => {
//                 *self.preview_open.borrow_mut() = x.into();
//             }
//         }
// 
//         self
//     }
// }

type ChatroomContext = UseReducerHandle<StateData>;

#[derive(Properties, PartialEq)]
pub struct ChildrenProps {
    pub children: Children
}

#[function_component]
pub fn ChatContext(props: &ChildrenProps) -> Html {

    let state = use_reducer(|| StateData::default());

    log!("reload context");
    log!(format!("{}", *state.preview_open.borrow()));

    html! {
        <ContextProvider<ChatroomContext> context={state} >
            {props.children.clone()}
        </ContextProvider<ChatroomContext>>
    }
}


#[function_component]
fn SubOne() -> Html {

    let ctx = use_context::<ChatroomContext>().unwrap();

    let onclick = {
        let ctx = ctx.clone();
        Callback::from(
            move |_| {
                ctx.dispatch(StateAction::SetPreviewOpen(true));
            }
        )
    };

    log!("reload sub one");

    html! {
        <>
            <div>{"SUB One"}</div>
            <button {onclick} >{"One button"}</button>
            <div>{format!("State from One: {}", ctx.preview_open.borrow().clone().to_string())}</div>
        </>
    }
}


#[function_component]
fn SubTwo() -> Html {

    let ctx = use_context::<ChatroomContext>().unwrap();

    let onclick = {
        let ctx = ctx.clone();
        Callback::from(
            move |_| {
                ctx.dispatch(StateAction::SetPreviewOpen(false));
            }
        )
    };

    log!("reload sub two");

    html! {
        <>
            <div>{"SUB Two"}</div>
            <button {onclick} >{"Two button"}</button>
            <div>{format!("State from Two: {}", ctx.preview_open.borrow().clone().to_string())}</div>
        </>
    }
}


#[function_component]
fn App() -> Html {

    html! {
        <div>
            <ChatContext>
                <SubOne />
                <SubTwo />
            </ChatContext>
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

Environment:

  • Yew version: [v0.21]
  • Rust version: [1.76.0, stable]
  • Target, if relevant: [wasm32-unknown-unknown]
  • Build tool, if relevant: [trunk]
  • OS, if relevant: [Windows 11]
  • Browser and version, if relevant: [Brave 1.62.162 Chromium: 121.0.6167.164 (Official Build) (64-bit)]

Questionnaire

  • I'm interested in fixing this myself but don't know where to start
  • I would like to fix and I have a solution
  • I don't have time to fix this right now, but maybe later
Dominant language
Rust
Stars
32.8k
Forks
1.5k
Avg merge
5h 34m
Merged PRs (30d)
2

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from yewstack/yew

All issues in yewstack/yew

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.