1# if/else: Conditional Rendering 2 3 4ArkTS provides conditional rendering. It supports the use of the **if**, **else**, and **else if** statements to display different content based on the application state. 5 6> **NOTE** 7> 8> This API can be used in ArkTS widgets since API version 9. 9 10## Rules of Use 11 12- The **if**, **else**, and **else if** statements are supported. 13 14- The condition statements following the **if** or **else if** statement can use state variables or normal variables. (Value change of state variables can trigger UI rendering in real time, but value change of normal variables cannot.) 15 16- Conditional statements can be used within a container component to build different child components. 17 18- Conditional statements are "transparent" when it comes to the parent-child relationship of components. Rules about permissible child components must be followed when there is one or more **if** statements between the parent and child components. 19 20- The build function inside each conditional branch must follow the special rules for build functions. Each of such build functions must create one or more components. An empty build function that creates no components will result in a syntax error. 21 22- Some container components impose restrictions on the type or number of child components. When conditional statements are used in such components, these restrictions also apply to the components to be created by using the conditional statements. For example, the child component of the [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md) container component supports only the [GridItem](../reference/apis-arkui/arkui-ts/ts-container-griditem.md) component. Therefore, only the **GridItem** can be used in the conditional statement within **Grid**. 23 24 25## Update Mechanism 26 27A conditional statement updates whenever a state variable used inside the **if** or **else if** condition changes. Specifically: 28 291. The conditional statement re-evaluates the conditions. If the evaluation of the conditions changes, steps 2 and 3 are performed. Otherwise, no follow-up operation is required. 30 312. The ArkUI framework removes all child components that have been built. 32 333. The ArkUI framework executes the build function of the conditional branch again to add the generated child component to its parent component. If an applicable **else** branch is missing, no new build function will be executed. 34 35A condition can include Typescript expressions. As for any expression inside build functions, such an expression must not change any application state. 36 37 38## Use Scenarios 39 40 41### Using if for Conditional Rendering 42 43 44```ts 45@Entry 46@Component 47struct ViewA { 48 @State count: number = 0; 49 50 build() { 51 Column() { 52 Text(`count=${this.count}`) 53 54 if (this.count > 0) { 55 Text(`count is positive`) 56 .fontColor(Color.Green) 57 } 58 59 Button('increase count') 60 .onClick(() => { 61 this.count++; 62 }) 63 64 Button('decrease count') 65 .onClick(() => { 66 this.count--; 67 }) 68 } 69 } 70} 71``` 72 73Each branch of the **if** statement includes a build function. Each of such build functions must create one or more components. On initial render, **if** will execute a build function and add the generated child component to its parent component. 74 75**if** updates whenever a state variable used inside the **if** or **else if** condition changes, and re-evaluates the conditions. If the evaluation of the conditions changes, it means that another branch of **if** needs to be built. In this case, the ArkUI framework will: 76 771. Remove all previously rendered components (of the earlier branch). 78 792. Execute the build function of the branch and add the generated child component to its parent component. 80 81In the preceding example, if **count** increases from 0 to 1, then **if** updates, the condition **count > 0** is re-evaluated, and the evaluation result changes from **false** to **true**. Therefore, the positive branch build function will be executed, which creates a **Text** component and adds it to the **Column** parent component. If **count** changes back to 0 later, then the **Text** component will be removed from the **Column** component. Since there is no **else** branch, no new build function will be executed. 82 83 84### if/else Statement and Child Component States 85 86This example involves an **if/else** statement and a child component with an \@State decorated variable. 87 88 89```ts 90@Component 91struct CounterView { 92 @State counter: number = 0; 93 label: string = 'unknown'; 94 95 build() { 96 Column({ space: 20 }) { 97 Text(`${this.label}`) 98 Button(`counter ${this.counter} +1`) 99 .onClick(() => { 100 this.counter += 1; 101 }) 102 } 103 .margin(10) 104 .padding(10) 105 .border({ width: 1 }) 106 } 107} 108 109@Entry 110@Component 111struct MainView { 112 @State toggle: boolean = true; 113 114 build() { 115 Column() { 116 if (this.toggle) { 117 CounterView({ label: 'CounterView #positive' }) 118 } else { 119 CounterView({ label: 'CounterView #negative' }) 120 } 121 Button(`toggle ${this.toggle}`) 122 .onClick(() => { 123 this.toggle = !this.toggle; 124 }) 125 } 126 .width('100%') 127 .justifyContent(FlexAlign.Center) 128 } 129} 130``` 131 132On first render, the child component **CounterView(label: 'CounterView \#positive')** is created. This child component carries the \@State decorated variable, named **counter**. When the **CounterView.counter** state variable is updated, the child component **CounterView(label: 'CounterView \#positive')** is re-rendered, with its state variable value preserved. When the value of the **MainView.toggle** state variable changes to **false**, the **if** statement inside the **MainView** parent component gets updated, and subsequently **CounterView(label: 'CounterView \#positive')** is removed. At the same time, a new child component **CounterView(label: 'CounterView \#negative')** is created, with the **counter** state variable set to the initial value **0**. 133 134> **NOTE** 135> 136> **CounterView(label: 'CounterView \#positive')** and **CounterView(label: 'CounterView \#negative')** are two distinct instances of the same custom component. When the **if** branch changes, there is no update to an existing child component or no preservation of state. 137 138The following example shows the required modifications if the value of **counter** needs to be preserved when the **if** condition changes: 139 140 141```ts 142@Component 143struct CounterView { 144 @Link counter: number; 145 label: string = 'unknown'; 146 147 build() { 148 Column({ space: 20 }) { 149 Text(`${this.label}`) 150 .fontSize(20) 151 Button(`counter ${this.counter} +1`) 152 .onClick(() => { 153 this.counter += 1; 154 }) 155 } 156 .margin(10) 157 .padding(10) 158 .border({ width: 1 }) 159 } 160} 161 162@Entry 163@Component 164struct MainView { 165 @State toggle: boolean = true; 166 @State counter: number = 0; 167 168 build() { 169 Column() { 170 if (this.toggle) { 171 CounterView({ counter: $counter, label: 'CounterView #positive' }) 172 } else { 173 CounterView({ counter: $counter, label: 'CounterView #negative' }) 174 } 175 Button(`toggle ${this.toggle}`) 176 .onClick(() => { 177 this.toggle = !this.toggle; 178 }) 179 } 180 .width('100%') 181 .justifyContent(FlexAlign.Center) 182 } 183} 184``` 185 186Here, the \@State decorated variable **counter** is owned by the parent component. Therefore, it is not destroyed when a **CounterView** component instance is destroyed. The **CounterView** component refers to the state by an \@Link decorator. The state must be moved from a child to its parent (or parent of parent) to avoid losing it when the conditional content (or repeated content) is destroyed. 187 188 189### Nested if Statements 190 191The nesting of **if** statements makes no difference to the rule about the parent component. 192 193 194```ts 195@Entry 196@Component 197struct CompA { 198 @State toggle: boolean = false; 199 @State toggleColor: boolean = false; 200 201 build() { 202 Column({ space: 20 }) { 203 Text('Before') 204 .fontSize(15) 205 if (this.toggle) { 206 Text('Top True, positive 1 top') 207 .backgroundColor('#aaffaa').fontSize(20) 208 // Inner if statement 209 if (this.toggleColor) { 210 Text('Top True, Nested True, positive COLOR Nested ') 211 .backgroundColor('#00aaaa').fontSize(15) 212 } else { 213 Text('Top True, Nested False, Negative COLOR Nested ') 214 .backgroundColor('#aaaaff').fontSize(15) 215 } 216 } else { 217 Text('Top false, negative top level').fontSize(20) 218 .backgroundColor('#ffaaaa') 219 if (this.toggleColor) { 220 Text('positive COLOR Nested ') 221 .backgroundColor('#00aaaa').fontSize(15) 222 } else { 223 Text('Negative COLOR Nested ') 224 .backgroundColor('#aaaaff').fontSize(15) 225 } 226 } 227 Text('After') 228 .fontSize(15) 229 Button('Toggle Outer') 230 .onClick(() => { 231 this.toggle = !this.toggle; 232 }) 233 Button('Toggle Inner') 234 .onClick(() => { 235 this.toggleColor = !this.toggleColor; 236 }) 237 } 238 .width('100%') 239 .justifyContent(FlexAlign.Center) 240 } 241} 242``` 243 244## FAQs 245 246### The if Branch Switching Protection Fails in the Animation Scenario 247 248Switching the **if/else** branch, which is used for data protection, in the animation and continuously using it, data access will throw an exception, causing a crash. 249 250Negative example: 251 252 253```ts 254class MyData { 255 str: string; 256 constructor(str: string) { 257 this.str = str; 258 } 259} 260@Entry 261@Component 262struct Index { 263 @State data1: MyData|undefined = new MyData("branch 0"); 264 @State data2: MyData|undefined = new MyData("branch 1"); 265 266 build() { 267 Column() { 268 if (this.data1) { 269 // If a Text is added or deleted in the animation, a default transition is added to Text. 270 // However, when the Text is deleted, the component lifecycle is prolonged after the default opacity transition is added. The Text component is not deleted until the transition animation is complete. 271 Text(this.data1.str) 272 .id("1") 273 } else if (this.data2) { 274 // If a Text is added or deleted in the animation, a default transition is added to Text. 275 Text(this.data2.str) 276 .id("2") 277 } 278 279 Button("play with animation") 280 .onClick(() => { 281 animateTo({}, ()=>{ 282 // Change the if condition in animateTo, the first-layer component under the if condition is transitioned by default in the animation. 283 if (this.data1) { 284 this.data1 = undefined; 285 this.data2 = new MyData("branch 1"); 286 } else { 287 this.data1 = new MyData("branch 0"); 288 this.data2 = undefined; 289 } 290 }) 291 }) 292 293 Button("play directlp") 294 .onClick(() => { 295 // Directly changing the if condition enables proper branch switching, and the default transition is not added even is . 296 if (this.data1) { 297 this.data1 = undefined; 298 this.data2 = new MyData("branch 1"); 299 } else { 300 this.data1 = new MyData("branch 0"); 301 this.data2 = undefined; 302 } 303 }) 304 }.width("100%") 305 .padding(10) 306 } 307} 308``` 309 310Positive example: 311 312Method 1: Add a null check protection to data when using data. For example, **Text(this.data1?.str)**. 313 314 315```ts 316class MyData { 317 str: string; 318 constructor(str: string) { 319 this.str = str; 320 } 321} 322@Entry 323@Component 324struct Index { 325 @State data1: MyData|undefined = new MyData("branch 0"); 326 @State data2: MyData|undefined = new MyData("branch 1"); 327 328 build() { 329 Column() { 330 if (this.data1) { 331 // If a Text is added or deleted in the animation, a default transition is added to Text. 332 // However, when the Text is deleted, the component lifecycle is prolonged after the default opacity transition is added. The Text component is not deleted until the transition animation is complete. 333 // Add a null check protection when using data. If data1 exists, use str in data1. 334 Text(this.data1?.str) 335 .id("1") 336 } else if (this.data2) { 337 // If a Text is added or deleted in the animation, a default transition is added to Text. 338 // Add a null check protection when using data. 339 Text(this.data2?.str) 340 .id("2") 341 } 342 343 Button("play with animation") 344 .onClick(() => { 345 animateTo({}, ()=>{ 346 // Change the if condition in animateTo, the first-layer component under the if condition is transitioned by default in the animation. 347 if (this.data1) { 348 this.data1 = undefined; 349 this.data2 = new MyData("branch 1"); 350 } else { 351 this.data1 = new MyData("branch 0"); 352 this.data2 = undefined; 353 } 354 }) 355 }) 356 }.width("100%") 357 .padding(10) 358 } 359} 360``` 361 362Method 2: Add the **transition(TransitionEffect.IDENTITY)** attribute to the component to be deleted in the **if/else** statement to prevent the system from adding the default transition. 363 364 365```ts 366class MyData { 367 str: string; 368 constructor(str: string) { 369 this.str = str; 370 } 371} 372@Entry 373@Component 374struct Index { 375 @State data1: MyData|undefined = new MyData("branch 0"); 376 @State data2: MyData|undefined = new MyData("branch 1"); 377 378 build() { 379 Column() { 380 if (this.data1) { 381 // Display the specified null transition effect in the root component of the if/else statement to avoid the default transition animation. 382 Text(this.data1.str) 383 .transition(TransitionEffect.IDENTITY) 384 .id("1") 385 } else if (this.data2) { 386 // Display the specified null transition effect in the root component of the if/else statement to avoid the default transition animation. 387 Text(this.data2.str) 388 .transition(TransitionEffect.IDENTITY) 389 .id("2") 390 } 391 392 Button("play with animation") 393 .onClick(() => { 394 animateTo({}, ()=>{ 395 // Change the if condition in animateTo, the first-layer component under the if condition is transitioned by default in the animation. 396 // The default transition will not be added if the specified transition has been displayed. 397 if (this.data1) { 398 this.data1 = undefined; 399 this.data2 = new MyData("branch 1"); 400 } else { 401 this.data1 = new MyData("branch 0"); 402 this.data2 = undefined; 403 } 404 }) 405 }) 406 }.width("100%") 407 .padding(10) 408 } 409} 410``` 411