Skip to main content

leptos/
for_loop.rs

1use crate::into_view::IntoView;
2use leptos_macro::component;
3use reactive_graph::{
4    owner::Owner,
5    signal::{ArcRwSignal, ReadSignal},
6    traits::Set,
7};
8use std::hash::Hash;
9use tachys::{
10    reactive_graph::OwnedView,
11    view::keyed::{keyed, SerializableKey},
12};
13
14/// Iterates over children and displays them, keyed by the `key` function given.
15///
16/// This is much more efficient than naively iterating over nodes with `.iter().map(|n| view! { ... })...`,
17/// as it avoids re-creating DOM nodes that are not being changed.
18///
19/// ```
20/// # use leptos::prelude::*;
21///
22/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
23/// struct Counter {
24///   id: usize,
25///   count: RwSignal<i32>
26/// }
27///
28/// #[component]
29/// fn Counters() -> impl IntoView {
30///   let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
31///
32///   view! {
33///     <div>
34///       <For
35///         // a function that returns the items we're iterating over; a signal is fine
36///         each=move || counters.get()
37///         // a unique key for each item
38///         key=|counter| counter.id
39///         // renders each item to a view
40///         children=move |counter: Counter| {
41///           view! {
42///             <button>"Value: " {move || counter.count.get()}</button>
43///           }
44///         }
45///       />
46///     </div>
47///   }
48/// }
49/// ```
50///
51/// For convenience, you can also choose to write template code directly in the `<For>`
52/// component, using the `let` syntax:
53///
54/// ```
55/// # use leptos::prelude::*;
56///
57/// # #[derive(Copy, Clone, Debug, PartialEq, Eq)]
58/// # struct Counter {
59/// #   id: usize,
60/// #   count: RwSignal<i32>
61/// # }
62/// #
63/// # #[component]
64/// # fn Counters() -> impl IntoView {
65/// #   let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
66/// #
67///   view! {
68///     <div>
69///         <For
70///           each=move || counters.get()
71///           key=|counter| counter.id
72///           let(counter)
73///         >
74///             <button>"Value: " {move || counter.count.get()}</button>
75///         </For>
76///     </div>
77///   }
78/// # }
79/// ```
80///
81/// The `let` syntax also supports destructuring the pattern of your data.
82/// `let((one, two))` in the case of tuples, and `let(Struct { field_one, field_two })`
83/// in the case of structs.
84///
85/// ```
86/// # use leptos::prelude::*;
87///
88/// # #[derive(Copy, Clone, Debug, PartialEq, Eq)]
89/// # struct Counter {
90/// #   id: usize,
91/// #   count: RwSignal<i32>
92/// # }
93/// #
94/// # #[component]
95/// # fn Counters() -> impl IntoView {
96/// #   let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
97/// #
98///   view! {
99///     <div>
100///         <For
101///           each=move || counters.get()
102///           key=|counter| counter.id
103///           let(Counter { id, count })
104///         >
105///             <button>"Value: " {move || count.get()}</button>
106///         </For>
107///     </div>
108///   }
109/// # }
110/// ```
111#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
112#[component]
113pub fn For<IF, I, T, EF, N, KF, K>(
114    /// Items over which the component should iterate.
115    each: IF,
116    /// A key function that will be applied to each item.
117    key: KF,
118    /// A function that takes the item, and returns the view that will be displayed for each item.
119    children: EF,
120) -> impl IntoView
121where
122    IF: Fn() -> I + Send + 'static,
123    I: IntoIterator<Item = T> + Send + 'static,
124    EF: Fn(T) -> N + Send + Clone + 'static,
125    N: IntoView + 'static,
126    KF: Fn(&T) -> K + Send + Clone + 'static,
127    K: Eq + Hash + SerializableKey + 'static,
128    T: Send + 'static,
129{
130    // this takes the owner of the For itself
131    // this will end up with N + 1 children
132    // 1) the effect for the `move || keyed(...)` updates
133    // 2) an owner for each child
134    //
135    // this means
136    // a) the reactive owner for each row will not be cleared when the whole list updates
137    // b) context provided in each row will not wipe out the others
138    let parent = Owner::current().expect("no reactive owner");
139    let children = move |_, child| {
140        let owner = parent.with(Owner::new);
141        let view = owner.with(|| children(child));
142        (drop, OwnedView::new_with_owner(view, owner))
143    };
144    move || keyed(each(), key.clone(), children.clone())
145}
146
147/// Iterates over children and displays them, keyed by the `key` function given.
148///
149/// Compared with For, it has an additional index parameter, which can be used to obtain the current index in real time.
150///
151/// This is much more efficient than naively iterating over nodes with `.iter().map(|n| view! { ... })...`,
152/// as it avoids re-creating DOM nodes that are not being changed.
153///
154/// ```
155/// # use leptos::prelude::*;
156///
157/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
158/// struct Counter {
159///   id: usize,
160///   count: RwSignal<i32>
161/// }
162///
163/// #[component]
164/// fn Counters() -> impl IntoView {
165///   let (counters, set_counters) = create_signal::<Vec<Counter>>(vec![]);
166///
167///   view! {
168///     <div>
169///       <ForEnumerate
170///         // a function that returns the items we're iterating over; a signal is fine
171///         each=move || counters.get()
172///         // a unique key for each item
173///         key=|counter| counter.id
174///         // renders each item to a view
175///         children={move |index: ReadSignal<usize>, counter: Counter| {
176///           view! {
177///             <button>{move || index.get()} ". Value: " {move || counter.count.get()}</button>
178///           }
179///         }}
180///       />
181///     </div>
182///   }
183/// }
184/// ```
185#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
186#[component]
187pub fn ForEnumerate<IF, I, T, EF, N, KF, K>(
188    /// Items over which the component should iterate.
189    each: IF,
190    /// A key function that will be applied to each item.
191    key: KF,
192    /// A function that takes the index and the item, and returns the view that will be displayed for each item.
193    children: EF,
194) -> impl IntoView
195where
196    IF: Fn() -> I + Send + 'static,
197    I: IntoIterator<Item = T> + Send + 'static,
198    EF: Fn(ReadSignal<usize>, T) -> N + Send + Clone + 'static,
199    N: IntoView + 'static,
200    KF: Fn(&T) -> K + Send + Clone + 'static,
201    K: Eq + Hash + SerializableKey + 'static,
202    T: Send + 'static,
203{
204    // this takes the owner of the For itself
205    // this will end up with N + 1 children
206    // 1) the effect for the `move || keyed(...)` updates
207    // 2) an owner for each child
208    //
209    // this means
210    // a) the reactive owner for each row will not be cleared when the whole list updates
211    // b) context provided in each row will not wipe out the others
212    let parent = Owner::current().expect("no reactive owner");
213    let children = move |index, child| {
214        let owner = parent.with(Owner::new);
215        let (index, set_index) = ArcRwSignal::new(index).split();
216        let view = owner.with(|| children(index.into(), child));
217        (
218            move |index| set_index.set(index),
219            OwnedView::new_with_owner(view, owner),
220        )
221    };
222    move || keyed(each(), key.clone(), children.clone())
223}
224
225/*
226#[cfg(test)]
227mod tests {
228    use crate::prelude::*;
229    use leptos_macro::view;
230    use tachys::{html::element::HtmlElement, prelude::ElementChild};
231
232    #[test]
233    fn creates_list() {
234        Owner::new().with(|| {
235            let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
236            let list: View<HtmlElement<_, _, _>> = view! {
237                <ol>
238                    <For each=move || values.get() key=|i| *i let:i>
239                        <li>{i}</li>
240                    </For>
241                </ol>
242            };
243            assert_eq!(
244                list.to_html(),
245                "<ol><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><!></\
246                 ol>"
247            );
248        });
249    }
250
251    #[test]
252    fn creates_list_enumerate() {
253        Owner::new().with(|| {
254            let values = RwSignal::new(vec![1, 2, 3, 4, 5]);
255            let list: View<HtmlElement<_, _, _>> = view! {
256                <ol>
257                    <ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
258                        <li>{move || index.get()}"-"{i}</li>
259                    </ForEnumerate>
260                </ol>
261            };
262            assert_eq!(
263                list.to_html(),
264                "<ol><li>0<!>-<!>1</li><li>1<!>-<!>2</li><li>2<!>-<!>3</li><li>3\
265                <!>-<!>4</li><li>4<!>-<!>5</li><!></ol>"
266            );
267
268            let list: View<HtmlElement<_, _, _>> = view! {
269                <ol>
270                    <ForEnumerate each=move || values.get() key=|i| *i let(index, i)>
271                        <li>{move || index.get()}"-"{i}</li>
272                    </ForEnumerate>
273                </ol>
274            };
275            values.set(vec![5, 4, 1, 2, 3]);
276            assert_eq!(
277                list.to_html(),
278                "<ol><li>0<!>-<!>5</li><li>1<!>-<!>4</li><li>2<!>-<!>1</li><li>3\
279                <!>-<!>2</li><li>4<!>-<!>3</li><!></ol>"
280            );
281        });
282    }
283}
284 */