dioxus icon indicating copy to clipboard operation
dioxus copied to clipboard

Nested for-loops in RSX render incorrectly

Open matthunz opened this issue 1 year ago • 1 comments

Problem

Nested for-loops in RSX seem to render in an incorrect order 🤔 This was initially found by @DogeDark on dioxus-web, and I was able to reproduce and simplify the example on dioxus-desktop. Freya however doesn't seem to be affected, so this seems like a post-v0.6 bug.

Looking at the implementation, the for-loop is converted to an iterator of VNodes. My hunch is having the outer iterator call the inner one is leading to the ordering bug but the logic looks correct to me.

https://github.com/DioxusLabs/dioxus/blob/c2b131f249cc757716ca2c4d917cc9b8db98aa08/packages/rsx/src/forloop.rs#L52C31-L52C32

Steps To Reproduce

#[component]
fn App() -> Element {
    let mut data = use_signal(|| vec![vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]);
    let mut next_value = use_signal(|| 10);

    let mut load_more = move || {
        let mut new_row = Vec::new();
        for _ in 0..10 {
            new_row.push(next_value());
            next_value += 1;
        }
        data.push(new_row);
    };

    rsx! {
        button {
            onclick: move |_| load_more(),
            "load more",
        }
        div {
            style: "display: grid; grid-template-columns: repeat(10, auto);",
 
            for row in data() {
                for column in row {
                    div{ key: "{column}", "{column}" }
                }
            }
        }
    }
}

Expected behavior Separating the above nested loops with a temporary variable results in the following correct output: Screenshot_2024-09-12_at_10 36 24_PM

Screenshots However with the nested loop: Screenshot_2024-09-12_at_10 35 09_PM

Environment:

  • Dioxus version: Latest git
  • Rust version: Nightly
  • App platform: Web + Desktop tested so far (so I think the issue resides in RSX itself)

Questionnaire

  • I'm interested in fixing this myself but don't know where to start

matthunz avatar Oct 01 '24 05:10 matthunz

If you add many lists to the first render, they render correctly:

use dioxus::prelude::*;

#[component]
fn App() -> Element {
    let mut data = use_signal(|| {
        (0..100)
            .step_by(10)
            .map(|i| (i..i + 10).collect::<Vec<_>>())
            .collect::<Vec<_>>()
    });

    let mut load_more = move || {
        let mut new_row = Vec::new();
        let last = {
            let binding = data.last().unwrap();
            *binding.last().unwrap()
        };
        for i in 1..11 {
            new_row.push(last + i);
        }
        data.push(new_row);
    };

    rsx! {
        button {
            onclick: move |_| load_more(),
            "load more",
        }
        div {
            style: "display: grid; grid-template-columns: repeat(10, auto);",

            for (i, row) in data.iter().enumerate() {
                for column in row.iter() {
                    div{ "{column}" }
                }
            }
        }
    }
}

fn main() {
    launch(App)
}

Diouxs SSR doesn't use core diffing and it also renders the lists correctly. I think this is a bug with keyed list diffing (here). If it was a bug with rsx, then the initial list would render incorrectly as well

ealmloff avatar Oct 02 '24 13:10 ealmloff