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 */