DEV Community

HarmonyOS
HarmonyOS

Posted on

How to Resolve Data Inconsistency Between Parent and Child Components

Read the original article:How to Resolve Data Inconsistency Between Parent and Child Components

Context

Variables decorated with @Prop establish a one-way synchronization relationship with the parent component. When the data source changes, the variables decorated with @Prop will update and override all local changes. Therefore, the synchronization of values is from the parent component to the child component, and changes in the child component's values will not be synchronized back to the parent component.

LocalStorage is an in-memory "database" provided by ArkTS for storing page-level state variables. Applications can create multiple LocalStorage instances, which can be shared within a page or across pages and UIAbility instances through the getShared interface.

Variables decorated with @Link establish a two-way data binding with the corresponding data source in their parent component. When one side changes, the other side will also be updated synchronously.

Description

Communication between parent and child components involves the parent component using the @State decorator to modify a variable and provide a default cached value, while the child component uses @Prop to receive values passed from the parent component. The parent component initiates an RPC request in the aboutToAppear lifecycle method. When the requested value has not yet returned, the child component uses the cached value from the parent component. Once the RPC request value is returned, the child component synchronously updates with the returned value. However, a breakpoint reveals that after the parent component's RPC request value is returned, the value is not synchronously refreshed in the child component's aboutToAppear lifecycle method. Is there a way for @Prop to synchronously update, or are there other methods for synchronous updates?

Solution

The reason for the phenomenon is that the parent component's RPC request did not return before the child component's aboutToAppear was executed.

Option One:

The issue can be resolved by creating a page-level UI state storage through LocalStorage. Since LocalStorage supports state sharing among multiple pages within a UIAbility instance, the parameters received by the @Entry decorator can share the same LocalStorage instance across pages. The steps are as follows:
1.Create a LocalStorage instance.

   let localStorageA: LocalStorage = new LocalStorage();
Enter fullscreen mode Exit fullscreen mode

2.The setOrCreate method of the LocalStorage instance sets the cached value.

   localStorageA.setOrCreate('isPT', true);
Enter fullscreen mode Exit fullscreen mode

3.The parent component passes the LocalStorage instance to the child component.

   @Builder
   PageMap(name: string) {
     if (name === 'pageOne') {
       pageOneStack({}, localStorageA)
     }
   }
Enter fullscreen mode Exit fullscreen mode

4.In the child component, bidirectional data synchronization is established between the property corresponding to the key in LocalStorage and the key using @LocalStorageLink(key).

   @LocalStorageLink('isPT') isPT: boolean = true;
Enter fullscreen mode Exit fullscreen mode

5.The parent component's aboutToAppear simulates an RPC asynchronous request.

   aboutToAppear(): void {
     setTimeout(() => {
       localStorageA.setOrCreate('isPT', false)
     },2000)
   }
Enter fullscreen mode Exit fullscreen mode

The complete example is as follows:

   let localStorageA: LocalStorage = new LocalStorage();
   localStorageA.setOrCreate('isPT', true);

   @Entry
   @Component
   struct MyNavigationTestStack {
     @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();

     @Builder
     PageMap(name: string) {
       if (name === 'pageOne') {
         pageOneStack({}, localStorageA)
       }
     }

     aboutToAppear(): void {
       setTimeout(() => {
         localStorageA.setOrCreate('isPT', false)
       }, 5000)
     }

     build() {
       Column({ space: 5 }) {
         Navigation(this.pageInfo) {
           Column() {
             Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
               .width('80%')
               .height(40)
               .margin(20)
               .onClick(() => {
                 this.pageInfo.pushPath({ name: 'pageOne' });
               })
           }
         }
         .title('NavIndex')
         .navDestination(this.PageMap)
         .mode(NavigationMode.Stack)
         .borderWidth(1)
       }
     }
   }

   @Component
   struct pageOneStack {
     @Consume('pageInfo') pageInfo: NavPathStack;
     @LocalStorageLink('isPT') isPT: boolean = true;

     build() {
       NavDestination() {
         Column() {
           NavigationContentMsgStack()
           Text(`${this.isPT}`)
         }.width('100%').height('100%')
       }
       .title('pageOne')
       .onBackPressed(() => {
         this.pageInfo.pop();
         return true;
       })
     }
   }

   @Component
   struct NavigationContentMsgStack {
     @LocalStorageLink('isPT') isPT: boolean = true

     build() {
       Column() {
         Text(`isPT: ${this.isPT}`)
           .fontSize(30)
           .fontWeight(FontWeight.Bold)
       }
     }
   }
Enter fullscreen mode Exit fullscreen mode

Option Two:

Use a state variable decorated with @Link to receive the value passed from the parent component.

The complete code is as follows:

@Entry
@Component
struct MyNavigationTestStack {
  pageInfo: NavPathStack = new NavPathStack();
  @State isPT: boolean = true

  @Builder
  PageMap(name: string) {
    if (name === 'pageOne') {
      pageOneStack({ isPT: this.isPT })
    }
  }

  aboutToAppear(): void {
    setTimeout(() => {
      this.isPT = false
    }, 5000)
  }

  build() {
    Column({ space: 5 }) {
      Navigation(this.pageInfo) {
        Column() {
          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
            .width('80%')
            .height(40)
            .margin(20)
            .onClick(() => {
              this.pageInfo.pushPath({ name: 'pageOne' });
            })
        }
      }
      .title('NavIndex')
      .mode(NavigationMode.Stack)
      .borderWidth(1)
      .navDestination(this.PageMap)
    }
  }
}

@Component
struct pageOneStack {
  pageInfo: NavPathStack = new NavPathStack()
  @Link isPT: boolean

  build() {
    NavDestination() {
      Column() {
        NavigationContentMsgStack({ isPT: this.isPT })
        Text(`${this.isPT}`)
      }.width('100%').height('100%')
    }
    .title('pageOne')
    .onReady((value) => {
      this.pageInfo = value.pathStack
    })
    .onBackPressed(() => {
      this.pageInfo.pop();
      return true;
    })
  }
}

@Component
struct NavigationContentMsgStack {
  @Link isPT: boolean

  build() {
    Column() {
      Text(`isPT: ${this.isPT}`)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • LocalStorage supports state sharing among multiple pages within a UIAbility instance. Use it to share data.
  • Or, use a state variable decorated with @Link to receive the value passed from the parent component.

Written by Mehmet Karaaslan

Top comments (0)