1# ForEach: Rendering of Repeated Content 2 3 4**ForEach** enables repeated content based on array-type data. 5 6> **NOTE** 7> 8> Since API version 9, this API is supported in ArkTS widgets. 9 10## API Description 11 12 13```ts 14ForEach( 15 arr: Array, 16 itemGenerator: (item: Array, index?: number) => void, 17 keyGenerator?: (item: Array, index?: number): string => string 18) 19``` 20 21 22| Name | Type | Mandatory | Description | 23| ------------- | ---------------------------------------- | ---- | ---------------------------------------- | 24| arr | Array | Yes | An array, which can be empty, in which case no child component is created. The functions that return array-type values are also allowed, for example, **arr.slice (1, 3)**. The set functions cannot change any state variables including the array itself, such as **Array.splice**, **Array.sort**, and **Array.reverse**.| 25| itemGenerator | (item: any, index?: number) => void | Yes | A lambda function used to generate one or more child components for each data item in an array. Each component and its child component list must be contained in parentheses.<br>**NOTE**<br>- The type of the child component must be the one allowed inside the parent container component of **ForEach**. For example, a **\<ListItem>** child component is allowed only when the parent container component of **ForEach** is **\<List>**.<br>- The child build function is allowed to return an **if** or another **ForEach**. **ForEach** can be placed inside **if**.<br>- The optional **index** parameter should only be specified in the function signature if used in its body.| 26| keyGenerator | (item: any, index?: number) => string | No | An anonymous function used to generate a unique and fixed key value for each data item in an array. This key-value generator is optional. However, for performance reasons, it is strongly recommended that the key-value generator be provided, so that the development framework can better identify array changes. For example, if no key-value generator is provided, a reverse of an array will result in rebuilding of all nodes in **ForEach**.<br>**NOTE**<br>- Two items inside the same array must never work out the same ID.<br>- If **index** is not used, an item's ID must not change when the item's position within the array changes. However, if **index** is used, then the ID must change when the item is moved within the array.<br>- When an item is replaced by a new one (with a different value), the ID of the replaced and the ID of the new item must be different.<br>- When **index** is used in the build function, it should also be used in the ID generation function.<br>- The ID generation function is not allowed to mutate any component state.| 27 28 29## Restrictions 30 31- **ForEach** must be used in container components. 32 33- The type of the child component must be the one allowed inside the parent container component of **ForEach**. 34 35- The **itemGenerator** function can contain an **if/else** statement, and an **if/else** statement can contain **ForEach**. 36 37- The call sequence of **itemGenerator** functions may be different from that of the data items in the array. During the development, do not assume whether or when the **itemGenerator** and **keyGenerator** functions are executed. For example, the following example may not run properly: 38 39 ```ts 40 let obj: Object 41 ForEach(anArray.map((item1: Object, index1: number): Object => { 42 obj.i = index1 + 1 43 obj.data = item1 44 return obj; 45 }), 46 (item: string) => Text(`${item.i}. item.data.label`), 47 (item: string): string => { 48 return item.data.id.toString() 49 }) 50 ``` 51 52 53## Development Guidelines 54 55- Make no assumption on the order of item build functions. The execution order may not be the order of items inside the array. 56 57- Make no assumption either when items are built the first time. Currently initial render of **ForEach** builds all array items when the \@Component decorated component is rendered at the first time, but future framework versions might change this behaviour to a more lazy behaviour. 58 59- Using the **index** parameter has severe negative impact on the UI update performance. Avoid using this parameter whenever possible. 60 61- If the **index** parameter is used in the item generator function, it must also be used in the item index function. Otherwise, the framework counts in the index when generating the ID. By default, the index is concatenated to the end of the ID. 62 63 64## Application Scenarios 65 66 67### Simple ForEach Example 68 69This example creates three **\<Text>** and **\<Divide>** components based on the **arr** data. 70 71 72```ts 73@Entry 74@Component 75struct MyComponent { 76 @State arr: number[] = [10, 20, 30]; 77 78 build() { 79 Column({ space: 5 }) { 80 Button('Reverse Array') 81 .onClick(() => { 82 this.arr.reverse(); 83 }) 84 ForEach(this.arr, (item: number) => { 85 Text(`item value: ${item}`).fontSize(18) 86 Divider().strokeWidth(2) 87 }, (item: number) => item.toString()) 88 } 89 } 90} 91``` 92 93 94### Complex ForEach Example 95 96 97```ts 98@Component 99struct CounterView { 100 @State label: string = ""; 101 @State count: number = 0; 102 103 build() { 104 Button(`${this.label}-${this.count} click +1`) 105 .width(300).height(40) 106 .backgroundColor('#a0ffa0') 107 .onClick(() => { 108 this.count++; 109 }) 110 } 111} 112 113@Entry 114@Component 115struct MainView { 116 @State arr: number[] = Array.from(Array(10).keys()); // [0.,.9] 117 nextUnused: number = this.arr.length; 118 119 build() { 120 Column() { 121 Button(`push new item`) 122 .onClick(() => { 123 this.arr.push(this.nextUnused++) 124 }) 125 .width(300).height(40) 126 Button(`pop last item`) 127 .onClick(() => { 128 this.arr.pop() 129 }) 130 .width(300).height(40) 131 Button(`prepend new item (unshift)`) 132 .onClick(() => { 133 this.arr.unshift(this.nextUnused++) 134 }) 135 .width(300).height(40) 136 Button(`remove first item (shift)`) 137 .onClick(() => { 138 this.arr.shift() 139 }) 140 .width(300).height(40) 141 Button(`insert at pos ${Math.floor(this.arr.length / 2)}`) 142 .onClick(() => { 143 this.arr.splice(Math.floor(this.arr.length / 2), 0, this.nextUnused++); 144 }) 145 .width(300).height(40) 146 Button(`remove at pos ${Math.floor(this.arr.length / 2)}`) 147 .onClick(() => { 148 this.arr.splice(Math.floor(this.arr.length / 2), 1); 149 }) 150 .width(300).height(40) 151 Button(`set at pos ${Math.floor(this.arr.length / 2)} to ${this.nextUnused}`) 152 .onClick(() => { 153 this.arr[Math.floor(this.arr.length / 2)] = this.nextUnused++; 154 }) 155 .width(300).height(40) 156 ForEach(this.arr, 157 (item: string) => { 158 CounterView({ label: item.toString() }) 159 }, 160 (item: string) => item.toString() 161 ) 162 } 163 } 164} 165``` 166 167**MainView** has an \@State decorated array of numbers. Adding, deleting, and replacing array items are observed mutation events. Whenever one of these events occurs, **ForEach** in **MainView** is updated. 168 169The item index function creates a unique and persistent ID for each array item. The ArkUI framework uses this ID to determine whether an item in the array changes. As long as the ID is the same, the item value is assumed to remain unchanged, but its index position may have changed. For this mechanism to work, different array items cannot have the same ID. 170 171Using the item ID obtained through computation, the framework can distinguish newly created, removed, and retained array items. 172 1731. The framework removes UI components for a removed array item. 174 1752. The framework executes the item build function only for newly added array items. 176 1773. The framework does not execute the item build function for retained array items. If the item index within the array has changed, the framework will just move its UI components according to the new order, but will not update that UI components. 178 179The item index function is recommended, but optional. The generated IDs must be unique. This means that the same ID must not be computed for any two items within the array. The ID must be different even if the two array items have the same value. 180 181If the array item value changes, the ID must be changed. 182As mentioned earlier, the ID generation function is optional. The following example shows **ForEach** without the item index function: 183 184 ```ts 185let list: Object 186ForEach(this.arr, 187 (item: Object): string => { 188 list.label = item.toString(); 189 CounterView(list) 190 } 191 ) 192 ``` 193 194If no item ID function is provided, the framework attempts to intelligently detect array changes when updating **ForEach**. However, it might remove child components and re-execute the item build function for array items that have been moved (with indexes changed). In the preceding example, this changes the application behavior in regard to the **counter** state of **CounterView**. When a new **CounterView** instance is created, the value of **counter** is initialized with **0**. 195 196 197### Example of ForEach Using \@ObjectLink 198 199If your application needs to preserve the state of repeated child components, you can use \@ObjectLink to enable the state to be "pushed up the component tree." 200 201 202```ts 203let NextID: number = 0; 204 205@Observed 206class MyCounter { 207 public id: number; 208 public c: number; 209 210 constructor(c: number) { 211 this.id = NextID++; 212 this.c = c; 213 } 214} 215 216@Component 217struct CounterView { 218 @ObjectLink counter: MyCounter; 219 label: string = 'CounterView'; 220 221 build() { 222 Button(`CounterView [${this.label}] this.counter.c=${this.counter.c} +1`) 223 .width(200).height(50) 224 .onClick(() => { 225 this.counter.c += 1; 226 }) 227 } 228} 229 230@Entry 231@Component 232struct MainView { 233 @State firstIndex: number = 0; 234 @State counters: Array<MyCounter> = [new MyCounter(0), new MyCounter(0), new MyCounter(0), 235 new MyCounter(0), new MyCounter(0)]; 236 237 build() { 238 Column() { 239 ForEach(this.counters.slice(this.firstIndex, this.firstIndex + 3), 240 (item: MyCounter) => { 241 CounterView({ label: `Counter item #${item.id}`, counter: item }) 242 }, 243 (item: MyCounter) => item.id.toString() 244 ) 245 Button(`Counters: shift up`) 246 .width(200).height(50) 247 .onClick(() => { 248 this.firstIndex = Math.min(this.firstIndex + 1, this.counters.length - 3); 249 }) 250 Button(`counters: shift down`) 251 .width(200).height(50) 252 .onClick(() => { 253 this.firstIndex = Math.max(0, this.firstIndex - 1); 254 }) 255 } 256 } 257} 258``` 259 260When the value of **firstIndex** is increased, **ForEach** within **Mainview** is updated, and the **CounterView** child component associated with the item ID **firstIndex-1** is removed. For the array item whose ID is **firstindex + 3**, a new** CounterView** child component instance is created. The value of the state variable **counter** of the **CounterView** child component is preserved by the **Mainview** parent component. Therefore, **counter** is not rebuilt when the **CounterView** child component instance is rebuilt. 261 262> **NOTE** 263> 264> The most common mistake application developers make in connection with **ForEach** is that the ID generation function returns the same value for two array items, especially in the Array\<number> scenario. 265 266 267### Nested Use of ForEach 268 269While nesting **ForEach** inside another **ForEach** in the same component is allowed, it is not recommended. It is better to split the component into two and have each build function include just one ForEach. The following is a poor example of nested use of **ForEach**. 270 271 272```ts 273class Month { 274 year: number; 275 month: number; 276 days: number[]; 277 278 constructor(year: number, month: number, ...days: number[]) { 279 this.year = year; 280 this.month = month; 281 this.days = days; 282 } 283} 284@Component 285struct CalendarExample { 286 // Simulate with six months. 287 arr28: Array<number> = Array(31).fill(0).map((_: number, i: number): number => i + 1); 288 arr30: Array<number> = Array(31).fill(0).map((_: number, i: number): number => i + 1); 289 arr31: Array<number> = Array(31).fill(0).map((_: number, i: number): number => i + 1); 290 @State calendar : Month[] = [ 291 new Month(2020, 1, ...(this.arr31)), 292 new Month(2020, 2, ...(this.arr28)), 293 new Month(2020, 3, ...(this.arr31)), 294 new Month(2020, 4, ...(this.arr30)), 295 new Month(2020, 5, ...(this.arr31)), 296 new Month(2020, 6, ...(this.arr30)) 297 ] 298 build() { 299 Column() { 300 Button() { 301 Text('next month') 302 }.onClick(() => { 303 this.calendar.shift() 304 this.calendar.push(new Month(2020, 7, ...(this.arr31))) 305 }) 306 ForEach(this.calendar, 307 (item: Month) => { 308 ForEach(item.days, 309 (day : number) => { 310 // Build a date block. 311 }, 312 (day : number) => day.toString() 313 )// Inner ForEach 314 }, 315 (item: Month) => (item.year * 12 + item.month).toString() // This field is used together with the year and month as the unique ID of the month. 316 )// Outer ForEach 317 } 318 } 319} 320``` 321 322The preceding example has two issues: 323 3241. The code readability is poor. 325 3262. For a data structure of months and days of a year, the framework cannot observe the attribute changes to **Month** objects, including any changes to the **days** array. As a result, the inner **ForEach** will not update the date. 327 328The recommended application design is to split **Calendar** into **Year**, **Month**, and **Day** child components. Define a **Day** model class to hold information about a day and decorate the class with \@Observed. The **DayView** component uses an \@ObjectLink decorated variable to link to the data about a day. Perform the same operations on the **MonthView** and **Month** model classes. 329 330 331### Example of Using the Optional index Parameter in ForEach 332 333You can use the optional **index** parameter in item build and ID generation functions. 334 335 336```ts 337@Entry 338@Component 339struct ForEachWithIndex { 340 @State arr: number[] = [4, 3, 1, 5]; 341 342 build() { 343 Column() { 344 ForEach(this.arr, 345 (it: number, index) => { 346 Text(`Item: ${index} - ${it}`) 347 }, 348 (it: number, index) => { 349 return `${index} - ${it}` 350 } 351 ) 352 } 353 } 354} 355``` 356 357The correct construction of the ID generation function is essential. When **index** is used in the item generation function, it should also be used in the ID generation function to produce unique IDs and an ID for given source array item that changes when its index position within the array changes. 358 359This example also illustrates that the **index** parameter can cause significant performance degradation. If an item is moved in the source array without modification, the dependent UI still requires rebuilding because of the changed index. For example, with the use of index sorting, the array only requires the unmodified child UI node of **ForEach** to be moved to the correct slot, which is a lightweight operation for the framework. When **index** is used, all child UI nodes need to be rebuilt, which is much more heavy weight. 360