Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with unkeyed VDOM list items #479

Closed
sirrodgepodge opened this issue Mar 10, 2019 · 16 comments
Closed

Issue with unkeyed VDOM list items #479

sirrodgepodge opened this issue Mar 10, 2019 · 16 comments

Comments

@sirrodgepodge
Copy link

sirrodgepodge commented Mar 10, 2019

Description

First, I'm new to Rust and a project in Yew has been my path to learning since I know JS front development pretty well already, super grateful that this project exists, thank you!

On to the question, in React there's a concept of the "key" attribute for items in lists which allow a user to add a unique ID to list items.

This ensures that nodes in lists can be properly tracked, since order alone is not enough. I see no equivalent here in Yew and sure enough if I remove a list item from the middle of the list, the DOM node at the end of the list is removed and the content from the subsequent list nodes all gets shifted up into the node one above them (as opposed to the node that I actually removed getting destroyed and the subsequent content all staying in the same place).

Expected Results

When item in the middle of the list is removed, remove the corresponding DOM node, don't alter subsequent DOM nodes in list.

<div
    class="__game_container",
>
    { for state.game_objects.iter().map(|k| {
      html! {
                    <RenderObj:
                        transition_on= render_obj.transition_on,
                        direction= render_obj.direction,
                        coords= render_obj.coords,
                    />
      }
    }) 
</div>

Actual Results

When VDOM node from the middle of the list is removed, in the actual DOM a node is removed from the end of the list and all the content that was in nodes following the removed VDOM node gets shifted up by one.

Context (Environment)

  • Rust: v1.35.0-nightly
  • yew: v0.7.0 (just switched to latest commit: 150e5af)
  • target: wasm32-unknown-unknown
  • cargo-web: v0.6.23
  • browser: Brave
@jstarry
Copy link
Member

jstarry commented Jul 24, 2019

@sirrodgepodge this would be a great feature to have! Would you like to take stab at implementing it?

@sirrodgepodge
Copy link
Author

thank you @jstarry, would absolutely love to, pretty slammed in the day job now so don't want to promise anything, if no one else does it someday I will though :)

@jeremyscatigna
Copy link

Would love to invest time on this 🙂 @jstarry would love to get some guidance if possible

@jstarry
Copy link
Member

jstarry commented Feb 18, 2020

@jeremyscatigna Great, this is a good task to get your feet wet. You're mostly going to be digging into 2 areas of the code base. The macro and virtual dom diffing parts.

I think we only need keyed elements inside "list" nodes. The virtual dom list node code is here. You'll need to make changes to VDiff::apply so that instead of assuming elements are in order, we search element keys to find matches.

As for keyed elements, I think any element could be keyed. So maybe we need a trait for KeyedElement so that we can check keys (if set).

The macro code is split up for components vs tags..

Component:

html! {
   <MyComponent key={1} />
}

Macro code for parsing component properties (we already have special handling for ref and children): https://github.com/yewstack/yew/blob/master/crates/macro/src/html_tree/html_component.rs#L392

Tag:

html! {
   <li key={1} />
}

Macro code for parsing tag attributes: https://github.com/yewstack/yew/blob/master/crates/macro/src/html_tree/html_tag/tag_attributes.rs
We have a lot more special handling here for various keywords

Another thing to consider is that we may wish to enforce that keys are used whenever rendering inside lists. I think we can punt that for v2.

@drdozer
Copy link

drdozer commented Feb 25, 2020

@jstarry do you mean that we need keys within VList? So for example, make it pub children: Vec<OptionallyKeyed<VNode>>?

Presumably in general keys can be any K: PartialEq? So how do we deal with the polymorphism here? Is VList now parameterised over key type?

Do we need to allow the user to specify if keys are require, absent, or optional in a given context? This feels like a lot of cognitive overhead. But the alternative is that we always have Option<K> to deal with, and in some cases a component may want to require that children are keyed.

@jstarry
Copy link
Member

jstarry commented Feb 26, 2020

@jstarry do you mean that we need keys within VList? So for example, make it pub children: Vec<OptionallyKeyed>?

Hmm, good question. I don't like the look of Vec<OptionallyKeyed<VNode>>. We probably want every VNode to implement a keyed() -> Option<String> method or something. We could add this to VDiff

Presumably in general keys can be any K: PartialEq? So how do we deal with the polymorphism here? Is VList now parameterised over key type?

I think we should use string keys to avoid that. So html! should accept keys that impl ToString

Do we need to allow the user to specify if keys are require, absent, or optional in a given context? This feels like a lot of cognitive overhead. But the alternative is that we always have Option to deal with, and in some cases a component may want to require that children are keyed.

Yeah this is more in line with what I am imagining. How about optional by default? I'm not sure how required keys would work at all yet. Might have to be a runtime check rather than compile time.

@jstarry
Copy link
Member

jstarry commented Feb 26, 2020

@jeremyscatigna another thing to consider is that we will likely need keyed lists for this change too. Analogous to React keyed fragments

@ZainlessBrombie
Copy link
Collaborator

@jeremyscatigna whats your status? I would consider picking this up, it's a great opportunity to get into writing macros and I think this feature is important for yew :)

@deep-gaurav
Copy link
Contributor

deep-gaurav commented Mar 27, 2020

Hi I implemented a very rough implementation of key to components,
Available in my fork
https://github.com/deep-gaurav/yew

If no one is working on it I can try to polish and optimize it (by using hashmaps i suppose) and convert to a PR

key can be used on either tag or component
Eg

{ for self.state.terminals.iter().map(|tab| {
      html! {
          <div key=tab.1.title.clone() class={
              if tab.1.is_active{
                  ""
              }else{
                  "is-hidden"
              }
          }>
              <TerminalComp  termid=tab.1.title.clone()/>
          </div>
      }
    })
}

@jstarry
Copy link
Member

jstarry commented Mar 28, 2020

@deep-gaurav I took a look at your changes and they look pretty great! I would love if you opened a PR for it :)

@deep-gaurav
Copy link
Contributor

@jstarry Currently in my implementation for VDiff for every element in Vlist children a linear search is made for same key in ancestor's children, this can be very expensive for list with many children i think.
Can the Vlist children's type be changed from Vec<VNode> to HashMap<String,Vec<VNode>>

this way the unkeyed children will be in vec with key String::default(), and can be handled same way as before, and keyed children can be accessed directly with their key?

@jstarry
Copy link
Member

jstarry commented Mar 29, 2020

Hm, it's important to keep the order of list children. Maybe a new data structure can be added with a map of key to children index?

mrh0057 added a commit to mrh0057/yew that referenced this issue Apr 4, 2020
@mrh0057
Copy link
Contributor

mrh0057 commented Apr 4, 2020

@jstarry @deep-gaurav I made some changes to use a hashmap. I was wondering though if keyed should be Option instead of String on the nodes?
Link to the repo:
https://github.com/mrh0057/yew/tree/keyed_update

mrh0057 added a commit to mrh0057/yew that referenced this issue Apr 4, 2020
Switched to using remove instead of get_mut() to make it so we don't
need to do a full scan.

Issue yewstack#479
mrh0057 added a commit to mrh0057/yew that referenced this issue Apr 4, 2020
@mrh0057
Copy link
Contributor

mrh0057 commented Apr 4, 2020

I was able to update the code to default to none on nodes without a key attribute.

mrh0057 added a commit to mrh0057/yew that referenced this issue Apr 5, 2020
Nodes with keys are in a hashmap and nodes without are in vector.
Added an error if duplicate keys are in the hashmap and outputs the duplicate key.

Issue yewstack#479
mrh0057 added a commit to mrh0057/yew that referenced this issue Apr 5, 2020
jstarry added a commit that referenced this issue Apr 13, 2020
* Add keys to components

* Switched to HashMap for added performance.  Fixed issue if their isn't a key on the nodes to not process the list.

Issue #479

* Ran cargo fmt.

* Deleted the node since we don't need it.

Switched to using remove instead of get_mut() to make it so we don't
need to do a full scan.

Issue #479

* Changed the key data type to Option<String>.

Issue #479.

* Removed rls config file.

* Fixed macro test to add the additional VChild parameter.

* Updated the code to handle cases where the root has a key.

Nodes with keys are in a hashmap and nodes without are in vector.
Added an error if duplicate keys are in the hashmap and outputs the duplicate key.

Issue #479

* Updated vlist to have key() attribute.

Issue #479

* Updated vlist without a key.

* Added a user defined key to vlist.

* Removed logging statement.

* Made the changes requested.  Still need to add macro tests.

* Updated the macro tests when the syntax changes.

* Ran cargo fmt.

* Switched to returning a reference for the key.

Removed the key comparision for vtag.

* Fixed clippy warning.

* Switch to use == to do the comparision to fix clippy error.

* Add clippy ignore

Co-authored-by: Deep Gaurav <deepgauravraj@gmail.com>
Co-authored-by: Justin Starry <justin.starry@icloud.com>
@lukerandall
Copy link
Contributor

@jstarry I believe this is closed by #1076

@jstarry
Copy link
Member

jstarry commented Apr 23, 2020

Yes it is, thanks!

@jstarry jstarry closed this as completed Apr 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants