1# ForEach: Rendering of Repeated Content 2 3 4**ForEach** enables repeated content based on array-type data. 5 6 7## API Description 8 9 10```ts 11ForEach( 12 arr: any[], 13 itemGenerator: (item: any, index?: number) => void, 14 keyGenerator?: (item: any, index?: number) => string 15) 16``` 17 18 19| Name | Type | Mandatory | Description | 20| ------------- | ---------------------------------------- | ---- | ---------------------------------------- | 21| 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**.| 22| 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 **\<LitemItem>** 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.| 23| 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.| 24 25 26## Restrictions 27 28- **ForEach** must be used in container components. 29 30- The type of the child component must be the one allowed inside the parent container component of **ForEach**. 31 32- The **itemGenerator** function can contain an **if/else** statement, and an **if/else** statement can contain **ForEach**. 33 34- 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: 35 36 ```ts 37 ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }), 38 item => Text(`${item.i}. item.data.label`), 39 item => item.data.id.toString()) 40 ``` 41 42 43## Recommendations 44 45- Make no assumption on the order of item build functions. The execution order may not be the order of items inside the array. 46 47- 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. 48 49- Using the **index** parameter has severe negative impact on the UI update performance. Avoid using this parameter whenever possible. 50 51- 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. 52 53 54## Application Scenarios 55 56 57### Simple ForEach Example 58 59This example creates three **\<Text>** and **\<Divide>** components based on the **arr** data. 60 61 62```ts 63@Entry 64@Component 65struct MyComponent { 66 @State arr: number[] = [10, 20, 30]; 67 68 build() { 69 Column({ space: 5 }) { 70 Button('Reverse Array') 71 .onClick(() => { 72 this.arr.reverse(); 73 }) 74 ForEach(this.arr, (item: number) => { 75 Text(`item value: ${item}`).fontSize(18) 76 Divider().strokeWidth(2) 77 }, (item: number) => item.toString()) 78 } 79 } 80} 81``` 82 83 84### Complex ForEach Example 85 86 87```ts 88@Component 89struct CounterView { 90 label: string; 91 @State count: number = 0; 92 93 build() { 94 Button(`${this.label}-${this.count} click +1`) 95 .width(300).height(40) 96 .backgroundColor('#a0ffa0') 97 .onClick(() => { 98 this.count++; 99 }) 100 } 101} 102 103@Entry 104@Component 105struct MainView { 106 @State arr: number[] = Array.from(Array(10).keys()); // [0.,.9] 107 nextUnused: number = this.arr.length; 108 109 build() { 110 Column() { 111 Button(`push new item`) 112 .onClick(() => { 113 this.arr.push(this.nextUnused++) 114 }) 115 .width(300).height(40) 116 Button(`pop last item`) 117 .onClick(() => { 118 this.arr.pop() 119 }) 120 .width(300).height(40) 121 Button(`prepend new item (unshift)`) 122 .onClick(() => { 123 this.arr.unshift(this.nextUnused++) 124 }) 125 .width(300).height(40) 126 Button(`remove first item (shift)`) 127 .onClick(() => { 128 this.arr.shift() 129 }) 130 .width(300).height(40) 131 Button(`insert at pos ${Math.floor(this.arr.length / 2)}`) 132 .onClick(() => { 133 this.arr.splice(Math.floor(this.arr.length / 2), 0, this.nextUnused++); 134 }) 135 .width(300).height(40) 136 Button(`remove at pos ${Math.floor(this.arr.length / 2)}`) 137 .onClick(() => { 138 this.arr.splice(Math.floor(this.arr.length / 2), 1); 139 }) 140 .width(300).height(40) 141 Button(`set at pos ${Math.floor(this.arr.length / 2)} to ${this.nextUnused}`) 142 .onClick(() => { 143 this.arr[Math.floor(this.arr.length / 2)] = this.nextUnused++; 144 }) 145 .width(300).height(40) 146 ForEach(this.arr, 147 (item) => { 148 CounterView({ label: item.toString() }) 149 }, 150 (item) => item.toString() 151 ) 152 } 153 } 154} 155``` 156 157**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. 158 159The 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. 160 161Using the item ID obtained through computation, the framework can distinguish newly created, removed, and retained array items. 162 1631. The framework removes UI components for a removed array item. 164 1652. The framework executes the item build function only for newly added array items. 166 1673. 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. 168 169The 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. 170 171If the array item value changes, the ID must be changed. 172As mentioned earlier, the ID generation function is optional. The following example shows **ForEach** without the item index function: 173 174 ```ts 175 ForEach(this.arr, 176 (item) => { 177 CounterView({ label: item.toString() }) 178 } 179 ) 180 ``` 181 182If 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**. 183 184 185### Example of ForEach Using \@ObjectLink 186 187If 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." 188 189 190```ts 191let NextID: number = 0; 192 193@Observed 194class MyCounter { 195 public id: number; 196 public c: number; 197 198 constructor(c: number) { 199 this.id = NextID++; 200 this.c = c; 201 } 202} 203 204@Component 205struct CounterView { 206 @ObjectLink counter: MyCounter; 207 label: string = 'CounterView'; 208 209 build() { 210 Button(`CounterView [${this.label}] this.counter.c=${this.counter.c} +1`) 211 .width(200).height(50) 212 .onClick(() => { 213 this.counter.c += 1; 214 }) 215 } 216} 217 218@Entry 219@Component 220struct MainView { 221 @State firstIndex: number = 0; 222 @State counters: Array<MyCounter> = [new MyCounter(0), new MyCounter(0), new MyCounter(0), 223 new MyCounter(0), new MyCounter(0)]; 224 225 build() { 226 Column() { 227 ForEach(this.counters.slice(this.firstIndex, this.firstIndex + 3), 228 (item) => { 229 CounterView({ label: `Counter item #${item.id}`, counter: item }) 230 }, 231 (item) => item.id.toString() 232 ) 233 Button(`Counters: shift up`) 234 .width(200).height(50) 235 .onClick(() => { 236 this.firstIndex = Math.min(this.firstIndex + 1, this.counters.length - 3); 237 }) 238 Button(`counters: shift down`) 239 .width(200).height(50) 240 .onClick(() => { 241 this.firstIndex = Math.max(0, this.firstIndex - 1); 242 }) 243 } 244 } 245} 246``` 247 248When 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. 249 250> **NOTE** 251> 252> 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. 253 254 255### Nested Use of ForEach 256 257While 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**. 258 259 260```ts 261class Month { 262 year: number; 263 month: number; 264 days: number[]; 265 266 constructor(year: number, month: number, days: number[]) { 267 this.year = year; 268 this.month = month; 269 this.days = days; 270 } 271} 272@Component 273struct CalendarExample { 274 // Simulate with six months. 275 @State calendar : Month[] = [ 276 new Month(2020, 1, [...Array(31).keys()]), 277 new Month(2020, 2, [...Array(28).keys()]), 278 new Month(2020, 3, [...Array(31).keys()]), 279 new Month(2020, 4, [...Array(30).keys()]), 280 new Month(2020, 5, [...Array(31).keys()]), 281 new Month(2020, 6, [...Array(30).keys()]) 282 ] 283 build() { 284 Column() { 285 Button() { 286 Text('next month') 287 }.onClick(() => { 288 this.calendar.shift() 289 this.calendar.push(new Month(year: 2020, month: 7, days: [...Array(31).keys()])) 290 }) 291 ForEach(this.calendar, 292 (item: Month) => { 293 ForEach(item.days, 294 (day : number) => { 295 // Build a date block. 296 }, 297 (day : number) => day.toString() 298 )// Inner ForEach 299 }, 300 (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. 301 )// Outer ForEach 302 } 303 } 304} 305``` 306 307The preceding example has two issues: 308 3091. The code readability is poor. 310 3112. 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. 312 313The 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. 314 315 316### Example of Using the Optional index Parameter in ForEach 317 318You can use the optional **index** parameter in item build and ID generation functions. 319 320 321```ts 322@Entry 323@Component 324struct ForEachWithIndex { 325 @State arr: number[] = [4, 3, 1, 5]; 326 327 build() { 328 Column() { 329 ForEach(this.arr, 330 (it, indx) => { 331 Text(`Item: ${indx} - ${it}`) 332 }, 333 (it, indx) => { 334 return `${indx} - ${it}` 335 } 336 ) 337 } 338 } 339} 340``` 341 342The 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. 343 344This 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. 345