#Implementing "deselect" in yew tutorial

1 messages · Page 1 of 1 (latest)

vestal moss
#

I'm trying to modify the yew tutorial, so as to deselect an list item when clicking it again.
Since the tuto gets #[derive(Clone)] on Video struct, the numerous .clone() get it copied all over, which makes it annoying to properly compare (when there can be distinct entries with identical contents in the list).
Changing this to use Rc<Video> seemed natural: I ought to be able to compare with Rc::ptr_eq.
So as a first step I change to store Rc<Video> as my properties like this, and that builds and runs well enough

Then I go on with my comparisons, and the Rcs always Rc::ptr_eq-compare different.

My best guess is it would come from the cloning of the selected_video "use_state" within the on_video_select callback.
But then, what would be the proper way to go?

Full work in this banch

vestal moss
#

In fact my problem rather seems to stem from the videos list being re-parsed on each app refresh.
What would be an idiomatic solution?
use_memo(props.force_reload, ...)?

rocky rune
#

havent gotten a good look at your solution (tip: the more precise the example (or repro) the more likely it is to get help), so here is how i would have it work, and you can take inspiration (it would probably have a lot of overlap with your actual code):

  • have a parent (video list component) that stores currently selected item (video item component) as an index (indices are Copy, and invalid index can be considered same as nothing selected)
  • parent sends (prop or context) a callback which takes in an index and sets currently selected. its logic is: prev == new -> None, else Some(new)
  • video item has to receive an index from parent, or callback should have index already baked in (new callback per item)
  • if videos list changes for whatever reason, use_memo can be used to reset the selection
    use_memo(videos, |_| currently_selected.set(None)); (memo needs no move)

bonus: Rc<Video> can be just Video if it is Clone, more precisely ImplicitClone - cheap to clone, when it consists only of rcs (AttrValue (IString, Rc<str>) instead of String (yew website highly recommends this), Callback (Rc<Fn>), all others are Rcs or ImplicitClone or Copy)

vestal moss
rocky rune
#

bane of all tutorials, having actual info and having perfect setup which would teach without having arguable decisions

vestal moss
#

Your suggestion is interesting, but even if the copy is not very expensive in itself, it has to go together with a memory allocation, which usually makes it much more expensive. So I still think Rc would still be useful, and once we can use it equality testing should be enough - and there is really no need to invalidate the selection on list change unless the selected item is removed.

Another reason I find the "list being re-parsed on each app refresh" is a bad idea, is that in the final version of the tutorial it includes fetching the data in addition to parsing it. That looks like an awfully bad idea for many use-cases.

rocky rune
#

whats parsing?

#

they fetch in an effect with deps=(), so it runs only once on component mount

vestal moss
#

oh right

#

so I have this problem only because I wanted to use a simpler version of the tuto, interesting...

#

ok, will have a new try with this in mind, thanks!

vestal moss
#

Indeed, once the data is deserialized as Vec<Rc<Video>> everything my original code works fine 🙂

#

thanks for putting me on the track!

vestal moss
#

Well, while this case backported on the tuto indeed works as expected, I still face a strange behavior with my somewhat-modified code.
However, that part of the code is really still the same, apart from a variable renaming.
Still, when adding detailed traces as follows, they show that after calling .set() on the use_state()-generated variable, it does indeed not get modified, as shown by the assertion triggering, after the "NEQ" trace confirming unit and prev_unit are not the same.

Any clue what could be at play here?

    let selected_unit = use_state(|| None);

    let on_unit_select = {
        let selected_unit = selected_unit.clone();
        Callback::from(move |unit: Rc<Unit>| {
            match (*selected_unit).clone() {
                None => { selected_unit.set(Some(Rc::clone(&unit))) },
                Some(prev_unit) => {
                    if Rc::<Unit>::ptr_eq(&prev_unit, &unit) {
                        selected_unit.set(None);
                    } else {
                        log::info!("{:p} NEQ -> {:p}", &*prev_unit, &*unit);
                        selected_unit.set(Some(Rc::clone(&unit)));
                        let new_unit = (*selected_unit).clone().unwrap();
                        assert!(! Rc::<Unit>::ptr_eq(&new_unit, &prev_unit));
                    }
                },
            }
        })
    };
rocky rune
#

like im always telling people who are using yew (and react too), state.set schedules a new render, for which the new state will be used

#

UseStateHandle stores an Rc to the state that was there when it was created

vestal moss
#

sounds like something to be added to the use_state documentation, indeed

rocky rune
#

but use_state rustdoc has all that info?

vestal moss
#

oh, that's likely what the "caution" message means, then - I must say the mention of "the use_reducer" in itself does not evoke much to me (sounds like an implementation detail?), and use_reducer is part of those for which reading the doc left me "duh, maybe I'll understand that when I'm more experienced with yew, but right now I don't understand at all.

rocky rune
#

use state = use reducer with only one message, that being the new state

#

but yeah its a ctrlc ctrlv bug

vestal moss
#

About use_reducer: I see "This hook is an alternative to use_state. It is used to handle component’s state and is used when complex actions needs to be performed on said state." is meant to explain what it's used for, but that did not cause the smallest spark in my mind - much too obscure to a beginner (with zero react background)

rocky rune
#

i would much rather use callbacks as opposed to reducers i guess

#

callback per such action instead of a message kind

vestal moss
#

I'm mostly trying to learn what tools are available, when reading about the reducer

#

Here I have a use_state because my code derives from the tuto. For some reason my implementation of deselection on the tuto does appear work - maybe it only worked by a curious side-effect?

rocky rune
# vestal moss I'm mostly trying to learn what tools are available, when reading about the redu...

theres this concept called struct components, which was what yew all had back in the day
all components receive Messages, and that causes something like fn update(&mut self, message: Self::Message) of the component to be called, in which you are allowed to update the state
reducers also accept messages, and change some state
so you can call any component a reducer
function components get turned into struct components with a message (all messages in fn components cause rerenders, so thats how hooks work) being ()

vestal moss
#

If read the doc as "The value held in the handle will reflect the value of at the time the handle is returned by the use_state." it is not that much informative either, as (as a beginner) it is not obvious when it is called or even by whom

rocky rune
#

wdym, you called it?

#

you wrote use_state - you called it

vestal moss
#

it's unclear what "the use_state" means then, so I guess that would be "The value held in the handle will reflect the value of at the time the handle is returned by use_state().", right?

#

from your answers (and previous compiler messages) (and now I read the prototype again I see it) I infer "the handle" is a UseStateHandle instance, but it would possibly help to state it more clearly in the doc.

rocky rune
#

prs welcome, im not a yewstack org member/yew maintainer

vestal moss
#

so that means that whatever the value I specify with .set() it is not supposed to be read back until in next call of the component's view() method?

#

I'm not sure I understand "It is possible that the handle does not dereference to an up to date value if you are moving it into a use_effect_with hook." either TBH 🙂

vestal moss
rocky rune
rocky rune
#

where state is UseStateHandle

#

if you move the handle somewhere, it still refers to old state

vestal moss
vestal moss
# rocky rune yeah

so when the doc for UseStateHandle::set() says "Replaces the value", it really not doing that? Else reading again I would expect to get the value just set...

rocky rune
#

i mean it does that eventually... lol

#

but yeah i remember that line confusing me at one point too, and that is despite me already knowing how it works and having worked with react

vestal moss
#

I know it's hard to write doc, but there seems to be an awful lot to do here 😦

#

even the page about "struct components" does not even say a word about them, just talks about "components" 😭

#

I guess that dates from when function components did not exist

#

the thing is there's so much to do with this doc it would take experienced yew users to fix all those small details - I don't have the xp here and I'd rather first focus on my project (and all the other projects I already have 😉

rocky rune
#

experienced people are blind to those struggles

vestal moss
#

not always

rocky rune
#

im probably not talking about the always case either

#

if someone who can fix it saw it, they wouldve already fixed it

vestal moss
#

I don't see much activity on the yew code btw (despite a good number of open PR and issues), that might also be linked

rocky rune
#

this moment as we speak the main two or three maintainers are away, and that leaves me and cecile (and probably more contributors) kinda stumped

#

although our projects (yewstack/implicit-clone and yewstack/yew-autoprops) can be developed separately luckily, even if we cant get much feedback from them

vestal moss
#

oh ok - temporary situation?

rocky rune
#

hm?

vestal moss
#

I mean, they're set to coming back?

rocky rune
#

ofc they arent gone forever lol

#

in fact i believe they setup an automatic yew release cycle, they will have to come back lol

vestal moss
# rocky rune this means that if you move that instance with the old state, it will still reta...

That part is still far from clear to me. First the doc says "It is possible that the handle does not dereference to an up to date value if ..." and the "if" does not seem to apply to my code. Is the "if" even useful? From what I see it may change (in the original tuto case) or not change (in my modified case). That would seem to have a HUGE impact on how to use it, like "you must extract the state value before (and should not do that after) updating it". That would sound strange.

#

But then, back to my original problem: what I learnt is that my attempt to assert is invalid (and indeed, the assert triggers as well when I add them to the tuto version).
So that leaves me with this case, where the state still has the same value on next iteration after I set it (and apparently it seems harder to pinpoint, because I cannot check in the current iteration that the change did get accepted) 🤷

#

With https://github.com/ydirson/test-yew-tutorial/tree/deselect changes persist:

INFO src/main.rs:111 choice: 0x110880 
INFO src/main.rs:114 None -> 0x110880

INFO src/main.rs:111 choice: 0x1112c8 
INFO src/main.rs:118 prev: 0x110880 
INFO src/main.rs:123 0x110880 NEQ -> 0x1112c8 

INFO src/main.rs:111 choice: 0x1112c8 
INFO src/main.rs:118 prev: 0x1112c8 
INFO src/main.rs:120 0x1112c8 EQ 0x1112c8 -> None 

INFO src/main.rs:111 choice: 0x1112c8 
INFO src/main.rs:114 None -> 0x1112c8 

INFO src/main.rs:111 choice: 0x111300 
INFO src/main.rs:118 prev: 0x1112c8 
INFO src/main.rs:123 0x1112c8 NEQ -> 0x111300 

INFO src/main.rs:111 choice: 0x110880 
INFO src/main.rs:118 prev: 0x111300 
INFO src/main.rs:123 0x111300 NEQ -> 0x110880

With https://github.com/ydirson/test-yew-tutorial/tree/opr-material-yew not only they do not persist ("prev" does not reflect the "->" value immediately before, but clicking one item causes 2 refreshes (which show the same behavior)

INFO src/main.rs:193 choice: 0x124bb0 
INFO src/main.rs:196 None -> 0x124bb0 

INFO src/main.rs:193 choice: 0x124de8 
INFO src/main.rs:201 prev: 0x124bb0 
INFO src/main.rs:206 0x124bb0 NEQ -> 0x124de8 
INFO src/main.rs:193 choice: 0x124de8 
INFO src/main.rs:201 prev: 0x124bb0 
INFO src/main.rs:206 0x124bb0 NEQ -> 0x124de8
#

Quite likely I missed something and did something wrong, in fact...

rocky rune
rocky rune
vestal moss
#

Here clicking on items 1 then 2 then 3, even more "spontaneous refreshes" (does this evokes anything?), and prev still never changes

INFO src/main.rs:193 choice: 0x124bb0 
INFO src/main.rs:196 None -> 0x124bb0 

INFO src/main.rs:193 choice: 0x124de8 
INFO src/main.rs:201 prev: 0x124bb0 
INFO src/main.rs:206 0x124bb0 NEQ -> 0x124de8 
INFO src/main.rs:193 choice: 0x124de8 
INFO src/main.rs:201 prev: 0x124bb0 
INFO src/main.rs:206 0x124bb0 NEQ -> 0x124de8 

INFO src/main.rs:193 choice: 0x125060 
INFO src/main.rs:201 prev: 0x124bb0 
INFO src/main.rs:206 0x124bb0 NEQ -> 0x125060 
INFO src/main.rs:193 choice: 0x124de8 
INFO src/main.rs:201 prev: 0x124bb0 
INFO src/main.rs:206 0x124bb0 NEQ -> 0x124de8 
INFO src/main.rs:193 choice: 0x125060 
INFO src/main.rs:201 prev: 0x124bb0 
INFO src/main.rs:206 0x124bb0 NEQ -> 0x125060 
#

The code being:

                        log::info!("{:p} NEQ -> {:p}", &*prev_unit, &*unit);
                        selected_unit.set(Some(Rc::clone(&unit)));

... Rust would not allow the data pointed to by unit to change here, there is no internal mutability, things should be straightforward

#

So the 2 identical statements selected_unit.set(Some(Rc::clone(&unit))) appear to not have the same effect. Not only that: this is here only the code with extensive debugging statements, the original code has a single statement for setting the state when there is no selection, or when we're changing the selection:

            match (*selected_unit).clone() {
                Some(prev_unit) if Rc::<Unit>::ptr_eq(&prev_unit, &unit)
                    => selected_unit.set(None),
                _ => selected_unit.set(Some(Rc::clone(&unit))),
            }

... so I would say any difference cannot come from different code paths, both go through _ =>

#

about those "spontaneous refreshes" (supposing they may have a link), do we have a way to know what led to this refresh?

rocky rune
# vestal moss about those "spontaneous refreshes" (supposing they may have a link), do we have...

all components are rerendered by only two causes: messages (fn update) and properties (fn changed)
since both in function components are hidden away by type Message = (), fn update(...) -> bool { true } and fn changed(...) -> bool { true }, there is no way to know what caused a rerender in function components
the thought is that in function components you are supposed to not do a rerender in first place (e.g. not calling unnecessary set), as opposed to cancelling a rerender upon a message or prop change (but there are things like use_state_eq which also prevent unnecessary rerenders)

vestal moss
#

what do you think about this state not updating?