1# ForEach: Rendering Repeated Content 2 3For details about API parameters, see [ForEach](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-foreach.md) APIs. 4 5**ForEach** enables rendering of repeated content based on array type data. It must be used in a container component, and the component it returns must be one allowed inside the container component. For example, for rendering of list items, **ForEach** must be used in the [List](../reference/apis-arkui/arkui-ts/ts-container-list.md) component. 6 7> **NOTE** 8> 9> This API is supported in ArkTS widgets since API version 9. 10 11## Key Generation Rules 12 13During **ForEach** rendering, the system generates a unique, persistent key for each array item to identify the corresponding component. When the key changes, the ArkUI framework considers that the array element has been replaced or modified and creates a component based on the new key. 14 15**ForEach** provides a parameter named **keyGenerator**, which is in effect a function through which you can customize key generation rules. If no **keyGenerator** function is defined, the ArkUI framework uses the default key generator, that is, **(item: Object, index: number) => { return index + '__' + JSON.stringify(item); }**. 16 17The ArkUI framework has a set of specific judgment rules for **ForEach** key generation, which are mainly associated with the second parameter **index** of the **itemGenerator** function and the second parameter **index** of the **keyGenerator** function. The following figure shows the logic of the key generation rules. 18 19**Figure 1** ForEach key generation rules 20 21 22> **NOTE** 23> 24> The ArkUI framework warns of duplicate keys. If duplicate keys exist during UI re-rendering, the framework may not work properly. For details, see [Rendering Result Not as Expected](#rendering-result-not-as-expected). 25 26## Component Creation Rules 27 28After the key generation rules are determined, the **itemGenerator** function – the second parameter in **ForEach** – creates a component for each array item of the data source based on the rules. There are two cases for creating a component: [initial rendering](#initial-rendering) and [non-initial rendering](#non-initial-rendering). 29 30### Initial Rendering 31 32When used for initial rendering, **ForEach** generates a unique key for each array item of the data source based on the key generation rules, and creates a component. 33 34```ts 35@Entry 36@Component 37struct Parent { 38 @State simpleList: Array<string> = ['one', 'two', 'three']; 39 40 build() { 41 Row() { 42 Column() { 43 ForEach(this.simpleList, (item: string) => { 44 ChildItem({ item: item }) 45 }, (item: string) => item) 46 } 47 .width('100%') 48 .height('100%') 49 } 50 .height('100%') 51 .backgroundColor(0xF1F3F5) 52 } 53} 54 55@Component 56struct ChildItem { 57 @Prop item: string; 58 59 build() { 60 Text(this.item) 61 .fontSize(50) 62 } 63} 64``` 65 66The figure below shows the effect. 67 68**Figure 2** Initial rendering when the ForEach data sources do not have the same key 69 70 71In the preceding code snippets, the key generation rule is the return value **item** of the **keyGenerator** function. During **ForEach** rendering, keys (**one**, **two**, and **three**) are generated in sequence for data source array items, and corresponding child items are created and rendered to the UI. 72 73When the keys generated for different data items are the same, the behavior of the framework is unpredictable. For example, in the following code, when data items with the same key **two** are rendered by **ForEach**, only one **ChildItem** component, instead of multiple components with the same key, is created. 74 75 ```ts 76 @Entry 77 @Component 78 struct Parent { 79 @State simpleList: Array<string> = ['one', 'two', 'two', 'three']; 80 81 build() { 82 Row() { 83 Column() { 84 ForEach(this.simpleList, (item: string) => { 85 ChildItem({ item: item }) 86 }, (item: string) => item) 87 } 88 .width('100%') 89 .height('100%') 90 } 91 .height('100%') 92 .backgroundColor(0xF1F3F5) 93 } 94 } 95 96 @Component 97 struct ChildItem { 98 @Prop item: string; 99 100 build() { 101 Text(this.item) 102 .fontSize(50) 103 } 104 } 105 ``` 106 107The figure below shows the effect. 108 109**Figure 3** Initial rendering when the ForEach data sources have the same key 110 111 112In this example, the final key value generation rule is **item**. When **ForEach** traverses the data source **simpleList** and finds the key **two** whose index is **1**, **ForEach** creates a component whose key is **two** based on the final key value generation rule and marks the component. When **ForEach** finds the key **two** whose index is **2**, it does not create a component, because the key of the current item is also **two** according to the final key generation rule. 113 114### Non-Initial Rendering 115 116When **ForEach** is used for re-rendering (non-initial rendering), it checks whether the newly generated key already exists in the previous rendering. If the key does not exist, a new component is created. If the key exists, no new component is created; instead, the component corresponding to the key is re-rendered. For example, in the following code snippet, the value of the third item of the array is changed to **"new three"** through the click event, which triggers **ForEach** to perform re-rendering. 117 118```ts 119@Entry 120@Component 121struct Parent { 122 @State simpleList: Array<string> = ['one', 'two', 'three']; 123 124 build() { 125 Row() { 126 Column() { 127 Text('Change Value of Third Array Item') 128 .fontSize(24) 129 .fontColor(Color.Red) 130 .onClick(() => { 131 this.simpleList[2] = 'new three'; 132 }) 133 134 ForEach(this.simpleList, (item: string) => { 135 ChildItem({ item: item }) 136 .margin({ top: 20 }) 137 }, (item: string) => item) 138 } 139 .justifyContent(FlexAlign.Center) 140 .width('100%') 141 .height('100%') 142 } 143 .height('100%') 144 .backgroundColor(0xF1F3F5) 145 } 146} 147 148@Component 149struct ChildItem { 150 @Prop item: string; 151 152 build() { 153 Text(this.item) 154 .fontSize(30) 155 } 156} 157``` 158 159The figure below shows the effect. 160 161**Figure 4** Re-rendering with ForEach 162 163 164From this example, you can see that @State can observe changes in the primitive array items of the **simpleList** data source. 165 1661. When any array item in **simpleList** changes, **ForEach** is triggered for re-rendering. 1672. **ForEach** traverses the new data source **['one', 'two', 'new three']** and generates the corresponding keys **one**, **two**, and **new three**. 1683. Because keys **one** and **two** already exist in the previous rendering, **ForEach** reuses the corresponding components and re-renders them. For the third array item **"new three"**, because a new key **new three** is generated for it based on the key generation rule **item**, **ForEach** creates a component for it. 169 170## Use Cases 171 172**ForEach** is typically used in several cases: [data source unchanged](#data-source-unchanged), [data source changed](#data-source-changed) (for example, when array items are inserted or deleted), and [properties of data source array items changed](#properties-of-data-source-array-items-changed). 173 174### Data Source Unchanged 175 176If the data source remains unchanged, it can of a primitive data type. For example, when a page is loading, the skeleton screen may be used. 177 178```ts 179@Entry 180@Component 181struct ArticleList { 182 @State simpleList: Array<number> = [1, 2, 3, 4, 5]; 183 184 build() { 185 Column() { 186 ForEach(this.simpleList, (item: number) => { 187 ArticleSkeletonView() 188 .margin({ top: 20 }) 189 }, (item: number) => item.toString()) 190 } 191 .padding(20) 192 .width('100%') 193 .height('100%') 194 } 195} 196 197@Builder 198function textArea(width: number | Resource | string = '100%', height: number | Resource | string = '100%') { 199 Row() 200 .width(width) 201 .height(height) 202 .backgroundColor('#FFF2F3F4') 203} 204 205@Component 206struct ArticleSkeletonView { 207 build() { 208 Row() { 209 Column() { 210 textArea(80, 80) 211 } 212 .margin({ right: 20 }) 213 214 Column() { 215 textArea('60%', 20) 216 textArea('50%', 20) 217 } 218 .alignItems(HorizontalAlign.Start) 219 .justifyContent(FlexAlign.SpaceAround) 220 .height('100%') 221 } 222 .padding(20) 223 .borderRadius(12) 224 .backgroundColor('#FFECECEC') 225 .height(120) 226 .width('100%') 227 .justifyContent(FlexAlign.SpaceBetween) 228 } 229} 230``` 231 232The figure below shows the effect. 233 234**Figure 5** Skeleton screen 235 236 237In this example, the data item is used as the key generation rule. Because the array items of the data source **simpleList** are different, the uniqueness of the keys can be ensured. 238 239### Data Source Changed 240 241If data source array item changes, for example, when an array item is inserted or deleted, or has its index changed, the data source should be of the object array type, and a unique ID of the object is used as the final key. For example, after a pull-to-refresh gesture is performed, newly obtained data items are added to the tail of the data source array, resulting in an increase in the length of the data source array. 242 243```ts 244class Article { 245 id: string; 246 title: string; 247 brief: string; 248 249 constructor(id: string, title: string, brief: string) { 250 this.id = id; 251 this.title = title; 252 this.brief = brief; 253 } 254} 255 256@Entry 257@Component 258struct ArticleListView { 259 @State isListReachEnd: boolean = false; 260 @State articleList: Array<Article> = [ 261 new Article('001','Article 1','Abstract'), 262 new Article('002','Article 2','Abstract'), 263 new Article('003','Article 3','Abstract'), 264 new Article('004','Article 4','Abstract'), 265 new Article('005','Article 5','Abstract'), 266 new Article ('006','Article 6','Abstract') 267 ] 268 269 loadMoreArticles() { 270 this.articleList.push(new Article('007','New article','Abstract'); 271 } 272 273 build() { 274 Column({ space: 5 }) { 275 List() { 276 ForEach(this.articleList, (item: Article) => { 277 ListItem() { 278 ArticleCard({ article: item }) 279 .margin({ top: 20 }) 280 } 281 }, (item: Article) => item.id) 282 } 283 .onReachEnd(() => { 284 this.isListReachEnd = true; 285 }) 286 .parallelGesture( 287 PanGesture({ direction: PanDirection.Up, distance: 80 }) 288 .onActionStart(() => { 289 if (this.isListReachEnd) { 290 this.loadMoreArticles(); 291 this.isListReachEnd = false; 292 } 293 }) 294 ) 295 .padding(20) 296 .scrollBar(BarState.Off) 297 } 298 .width('100%') 299 .height('100%') 300 .backgroundColor(0xF1F3F5) 301 } 302} 303 304@Component 305struct ArticleCard { 306 @Prop article: Article; 307 308 build() { 309 Row() { 310 Image($r('app.media.icon')) 311 .width(80) 312 .height(80) 313 .margin({ right: 20 }) 314 315 Column() { 316 Text(this.article.title) 317 .fontSize(20) 318 .margin({ bottom: 8 }) 319 Text(this.article.brief) 320 .fontSize(16) 321 .fontColor(Color.Gray) 322 .margin({ bottom: 8 }) 323 } 324 .alignItems(HorizontalAlign.Start) 325 .width('80%') 326 .height('100%') 327 } 328 .padding(20) 329 .borderRadius(12) 330 .backgroundColor('#FFECECEC') 331 .height(120) 332 .width('100%') 333 .justifyContent(FlexAlign.SpaceBetween) 334 } 335} 336``` 337 338The following figure shows the initial screen (on the left) and the screen after a pull-to-refresh gesture (on the right). 339 340**Figure 6** When the data source is changed 341 342 343In this example, the **ArticleCard** component functions as a child component of the **ArticleListView** component and receives an **Article** object through the @Prop decorator to render article widgets. 344 3451. When the list scrolls to the bottom, if the distance of finger movement exceeds the threshold 80, the **loadMoreArticle()** function is triggered. This function adds a new data item to the tail of the **articleList** data source, increasing the length of the data source. 3462. Because the data source is decorated by @State, the ArkUI framework can detect the change of the data source length and trigger **ForEach** for re-rendering. 347 348### Properties of Data Source Array Items Changed 349 350If the data source array items are of the Object type, property changes of these array items cannot be detected by the ArkUI framework, because the framework cannot detect property changes of array items of complex types when the array is decorated by @State. As a result, re-rendering by **ForEach** is not performed. To trigger **ForEach** to perform re-rendering, use the @Observed and @ObjectLink decorators. In the following example, clicking the Like icon on the article list changes the number of likes for an article. 351 352```ts 353@Observed 354class Article { 355 id: string; 356 title: string; 357 brief: string; 358 isLiked: boolean; 359 likesCount: number; 360 361 constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) { 362 this.id = id; 363 this.title = title; 364 this.brief = brief; 365 this.isLiked = isLiked; 366 this.likesCount = likesCount; 367 } 368} 369 370@Entry 371@Component 372struct ArticleListView { 373 @State articleList: Array<Article> = [ 374 new Article('001','Article 0','Abstract', false, 100), 375 new Article('002','Article 1','Abstract', false, 100), 376 new Article('003','Article 2','Abstract', false, 100), 377 new Article('004','Article 4','Abstract', false, 100), 378 new Article('005','Article 5','Abstract', false, 100), 379 new Article('006','Article 6','Abstract', false, 100), 380 ]; 381 382 build() { 383 List() { 384 ForEach(this.articleList, (item: Article) => { 385 ListItem() { 386 ArticleCard({ 387 article: item 388 }) 389 .margin({ top: 20 }) 390 } 391 }, (item: Article) => item.id) 392 } 393 .padding(20) 394 .scrollBar(BarState.Off) 395 .backgroundColor(0xF1F3F5) 396 } 397} 398 399@Component 400struct ArticleCard { 401 @ObjectLink article: Article; 402 403 handleLiked() { 404 this.article.isLiked = !this.article.isLiked; 405 this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1; 406 } 407 408 build() { 409 Row() { 410 Image($r('app.media.icon')) 411 .width(80) 412 .height(80) 413 .margin({ right: 20 }) 414 415 Column() { 416 Text(this.article.title) 417 .fontSize(20) 418 .margin({ bottom: 8 }) 419 Text(this.article.brief) 420 .fontSize(16) 421 .fontColor(Color.Gray) 422 .margin({ bottom: 8 }) 423 424 Row() { 425 Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked')) 426 .width(24) 427 .height(24) 428 .margin({ right: 8 }) 429 Text(this.article.likesCount.toString()) 430 .fontSize(16) 431 } 432 .onClick(() => this.handleLiked()) 433 .justifyContent(FlexAlign.Center) 434 } 435 .alignItems(HorizontalAlign.Start) 436 .width('80%') 437 .height('100%') 438 } 439 .padding(20) 440 .borderRadius(12) 441 .backgroundColor('#FFECECEC') 442 .height(120) 443 .width('100%') 444 .justifyContent(FlexAlign.SpaceBetween) 445 } 446} 447``` 448 449The following figure shows the initial screen (on the left) and the screen after the Like icon of Article 1 is clicked (on the right). 450 451**Figure 7** When properties of data source array items are changed 452 453 454In this example, the **Article** class is decorated by the @Observed decorator. The parent component **ArticleListView** passes an **Article** object instance to the child component **ArticleCard**, and the child component uses the @ObjectLink decorator to receive the instance. 455 4561. When the Like icon of Article 1 is clicked, the **handleLiked** function of the **ArticleCard** component is triggered. This function changes the values of the **isLiked** and **likesCount** properties of the **article** instance in the component pertaining to Article 1. 4572. Because **article** in the child component **ArticleCard** uses the @ObjectLink decorator, the parent and child components share the same article data. As such, the values of **isLiked** and **likedCounts** of the first array item of **articleList** in the parent component are changed synchronously. 4583. When the parent component detects property changes of the data source array items, **ForEach** is triggered for re-rendering. 4594. Here, the **ForEach** key generation rule is the **id** property value of the array item. If **ForEach** traverses the new data source and finds no changes in the **id** values, no component is created. 4605. When the **ArticleCard** component corresponding to the first array item is rendered, the obtained values of **isLiked** and **likesCount** are the new values. 461 462### Enabling Drag and Sort 463If **ForEach** is used in a list, and the **onMove** event is set, you can enable drag and sort for the list items. If an item changes the position after you drag and sort the data, the **onMove** event is triggered to report the original index and target index of the item. The data source needs to be modified in the **onMove** event based on the reported start index and target index. Before and after the data source is modified, the value of each item must remain unchanged to ensure that the drop animation can be executed properly. 464 465```ts 466@Entry 467@Component 468struct ForEachSort { 469 @State arr: Array<string> = []; 470 471 build() { 472 Row() { 473 List() { 474 ForEach(this.arr, (item: string) => { 475 ListItem() { 476 Text(item.toString()) 477 .fontSize(16) 478 .textAlign(TextAlign.Center) 479 .size({height: 100, width: "100%"}) 480 }.margin(10) 481 .borderRadius(10) 482 .backgroundColor("#FFFFFFFF") 483 }, (item: string) => item) 484 .onMove((from:number, to:number) => { 485 let tmp = this.arr.splice(from, 1); 486 this.arr.splice(to, 0, tmp[0]) 487 }) 488 } 489 .width('100%') 490 .height('100%') 491 .backgroundColor("#FFDCDCDC") 492 } 493 } 494 aboutToAppear(): void { 495 for (let i = 0; i < 100; i++) { 496 this.arr.push(i.toString()) 497 } 498 } 499} 500``` 501 502**Figure 8** Drag and sort in ForEach 503 504## Suggestions 505 506- To ensure unique keys for array items of the Object type, you are advised to use the unique IDs of objects as keys. 507- Avoid including the data item **index** in the final key generation rule to prevent [unexpected rendering results](#rendering-result-not-as-expected) and [deteriorated rendering performance](#deteriorated-rendering-performance). If including **index** is required, for example, when the list needs to be rendered based on the index, prepare for the performance loss resulting from component creation by **ForEach** to account for data source changes. 508- Data items of primitive data types do not have a unique ID. If you use the primitive data type itself as the key, you must ensure that the array items are not duplicate. In scenarios where the data source changes, you are advised to convert the array of a primitive data type into an array of the Object type with the **id** property, and then use the **id** property as the key generation rule. 509- For the preceding restriction rules, the **index** parameter is the final method for you to ensure the uniqueness of the keys. When modifying a data item, you need to use the index value to modify the data source because the **item** parameter in **itemGenerator** cannot be modified. In this way, the UI re-rendering is triggered. 510- Do not use **ForEach** together with [LazyForEach](./arkts-rendering-control-lazyforeach.md) in [List](../reference/apis-arkui/arkui-ts/ts-container-list.md), [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md), [Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md), and [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md). 511 512## Common Pitfalls 513 514Correct use of **ForEach** requires a clear understanding of the key generation rules. Incorrect use may cause functional issues, for example, [unexpected rendering results](#rendering-result-not-as-expected), or performance issues, for example, [deteriorated rendering performance](#deteriorated-rendering-performance). 515 516### Rendering Result Not as Expected 517 518In this example, the **KeyGenerator** function – the third parameter of **ForEach** – is set to use the string-type **index** property of the data source as the key generation rule. When **Insert an Item after the First Item** in the parent component **Parent** is clicked, an unexpected result is displayed. 519 520```ts 521@Entry 522@Component 523struct Parent { 524 @State simpleList: Array<string> = ['one', 'two', 'three']; 525 526 build() { 527 Column() { 528 Button() { 529 Text('Insert an Item after the First Item').fontSize(30) 530 } 531 .onClick(() => { 532 this.simpleList.splice(1, 0, 'new item'); 533 }) 534 535 ForEach(this.simpleList, (item: string) => { 536 ChildItem({ item: item }) 537 }, (item: string, index: number) => index.toString()) 538 } 539 .justifyContent(FlexAlign.Center) 540 .width('100%') 541 .height('100%') 542 .backgroundColor(0xF1F3F5) 543 } 544} 545 546@Component 547struct ChildItem { 548 @Prop item: string; 549 550 build() { 551 Text(this.item) 552 .fontSize(30) 553 } 554} 555``` 556 557The following figure shows the initial screen and the screen after **Insert an Item after the First Item** is clicked. 558 559**Figure 9** Rendering result not as expected 560 561 562When **ForEach** is used for initial rendering, the created keys are **0**, **1**, and **2** in sequence. 563 564After a new item is inserted, the data source **simpleList** changes to ['one','new item', 'two', 'three']. The ArkUI framework detects changes in the length of the @State decorated data source and triggers **ForEach** for re-rendering. 565 566**ForEach** traverses items in the new data source. When it reaches array item **one**, it generates key **0** for the item, and because the same key already exists, no new component is created. When **ForEach** reaches array item **new item**, it generates key **1** for the item, and because the same key already exists, no new component is created. When **ForEach** reaches array item **two**, it generates key **2** for the item, and because the same key already exists, no new component is created. When **ForEach** reaches array item **three**, it generates key **3** for the item, and because no same key exists, a new component **three** is created. 567 568In the preceding example, the final key generation rule includes **index**. While the expected rendering result is ['one','new item', 'two', 'three'], the actual rendering result is ['one', 'two', 'three', 'three']. Therefore, whenever possible, avoid including **index** in final key generation rule when using **ForEach**. 569 570### Deteriorated Rendering Performance 571 572In this example, the **KeyGenerator** function – the third parameter of **ForEach** – is left empty. According to the description in [Key Generation Rules](#key-generation-rules), the default key generation rule of the ArkUI framework is used. That is, the final key is the string **index + '__' + JSON.stringify(item)**. After **Insert an Item after the First Item** is clicked, **ForEach** recreates components for the second array item and all items after it. 573 574```ts 575@Entry 576@Component 577struct Parent { 578 @State simpleList: Array<string> = ['one', 'two', 'three']; 579 580 build() { 581 Column() { 582 Button() { 583 Text('Insert an Item after the First Item').fontSize(30) 584 } 585 .onClick(() => { 586 this.simpleList.splice(1, 0, 'new item'); 587 console.log(`[onClick]: simpleList is ${JSON.stringify(this.simpleList)}`); 588 }) 589 590 ForEach(this.simpleList, (item: string) => { 591 ChildItem({ item: item }) 592 }) 593 } 594 .justifyContent(FlexAlign.Center) 595 .width('100%') 596 .height('100%') 597 .backgroundColor(0xF1F3F5) 598 } 599} 600 601@Component 602struct ChildItem { 603 @Prop item: string; 604 605 aboutToAppear() { 606 console.log(`[aboutToAppear]: item is ${this.item}`); 607 } 608 609 build() { 610 Text(this.item) 611 .fontSize(50) 612 } 613} 614``` 615 616The following figure shows the initial screen and the screen after **Insert an Item after the First Item** is clicked. 617 618**Figure 10** Deteriorated rendering performance 619 620 621After **Insert an Item after the First Item** is clicked, DevEco Studio displays logs as shown in the figure below. 622 623**Figure 11** Logs indicating deteriorated rendering performance 624 625 626After a new item is inserted, **ForEach** creates the corresponding child items for the **new item**, **two**, and **three** array items, and executes the [aboutToAppear()](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear) callback. Below are the reasons: 627 6281. When **ForEach** is used for initial rendering, the created keys are **0__one**, **1__two** and **2__three** in sequence. 6292. After a new item is inserted, the data source **simpleList** changes to ['one','new item', 'two', 'three']. The ArkUI framework detects changes in the length of the @State decorated data source and triggers **ForEach** for re-rendering. 6303. **ForEach** traverses items in the new data source. When it reaches array item **one**, it generates key **0__one** for the item, and because the same key already exists, no new component is created. When **ForEach** reaches array item **new item**, it generates key **1__new item** for the item, and because no same key exists, a new component **new item** is created. When **ForEach** reaches array item **two**, it generates key **2__two** for the item, and because no same key exists, a new component **two** is created. When **ForEach** reaches array item **three**, it generates key **3__three** for the item, and because no same key exists, a new component **three** is created. 631 632Although the rendering result in this example is as expected, each time a new array item is inserted, **ForEach** recreates components for all array items following that array item. Because components are not reused, the performance experience can be poor when the data source contains a large volume of data or the component structure is complex. To sum up, whenever possible, do not leave the third parameter (the **KeyGenerator** function) of **ForEach** empty, or include **index** in the key generation rule. 633