SPFx React Component Property Pane Update Lag

831 views Asked by At

EDITED WITH CORRECT ANSWER

So I am customizing an SPFx component's property pane with a PropertyFieldSitePicker and a PropertyFieldListPicker. My issue is that the PropertyFieldMultiSelect lags by one selection when I select options from the PropertyFieldListPicker menu. However, if I make selections it will then update making me suspect it is a React state issue.

In the code below the following is the code logic

PropertyFieldListPicker('lists', {
  // ... more props
  onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
}),

then

protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
    if (newValue !== oldValue && propertyPath === 'lists') {
      this.loadPropertyPaneResources()
    }
  }

then

  // this gets modified by loadPropertyPaneResources below
  private _locationOptions: [];

  protected loadPropertyPaneResources(): Promise<void> {
    let listUrl = `...`
    //... more code

    return new Promise((resolve, reject) => {
      this.context.spHttpClient.get(listUrl, SPHttpClient.configurations.v1)
        .then((response: SPHttpClientResponse) => response.json())
        .then((responseJSON: any) => {
          this._locationOptions = responseJSON.value.map(item => {
            if (item.displayName) {
              return { key: item.Id, text: item.displayName }
            }
            return { key: item.Id, text: item.Title }
          })
          this.context.propertyPane.refresh()
          resolve()
        })
        // ... more code
    });
  }

then

PropertyFieldMultiSelect('selectedLocations', {
  key: 'selectedLocations',
  label: 'Select Office Locations',
  options: this._locationOptions, // from loadPropertyPaneResources above
  selectedKeys: this.properties.selectedLocations
}),

FULL COMPONENT CLASS

export default class WorldClockWebPart extends BaseClientSideWebPart<IWorldClockProps> {

  private _themeProvider: ThemeProvider
  private _themeVariant: IReadonlyTheme | undefined
  private _locationOptions: []

  private _handleThemeChangedEvent(args: ThemeChangedEventArgs): void {
    this._themeVariant = args.theme
    this.render()
  }

  protected loadPropertyPaneResources(): Promise<void> {
    let listUrl = `${this.context.pageContext.web.absoluteUrl}/_api/lists/GetByTitle('World Clock Locations')/items`;
    const { sites, lists } = this.properties

    if (sites && sites[0] && sites[0].url && lists) {
      listUrl = `${sites[0].url}/_api/lists(guid'${lists}')/items`;
    }

    return new Promise((resolve, reject) => {
      this.context.spHttpClient.get(listUrl, SPHttpClient.configurations.v1)
        .then((response: SPHttpClientResponse) => response.json())
        .then((responseJSON: any) => {
          this._locationOptions = responseJSON.value.map(item => {
            if (item.displayName) {
              return { key: item.Id, text: item.displayName }
            }
            return { key: item.Id, text: item.Title }
          });
          resolve();
        })
        .catch(error => {
          console.error(error);
          resolve();
        });
    });
  }

  protected onInit(): Promise<void> {
    // Consume the new ThemeProvider service
    this._themeProvider = this.context.serviceScope.consume(ThemeProvider.serviceKey)

    // If it exists, get the theme variant
    this._themeVariant = this._themeProvider.tryGetTheme()

    // Register a handler to be notified if the theme variant changes
    this._themeProvider.themeChangedEvent.add(this, this._handleThemeChangedEvent)

    return super.onInit()
  }

  public render(): void {
    const element: React.ReactElement<IWorldClockProps> = React.createElement(
      WorldClock,
      {
        title: this.properties.title,
        context: this.context,
        displayMode: this.displayMode,
        titlePlaceholder: this.properties.titlePlaceholder,
        themeVariant: this._themeVariant,
        updateProperty: (value: string) => {
          this.properties.title = value
        },
        locations: [],
        seeMoreUrl: this.properties.seeMoreUrl,
        selectedLocations: this.properties.selectedLocations,
        sites: this.properties.sites,
        lists: this.properties.lists, // Stores the list ID(s)
      }
    )
    ReactDom.render(element, this.domElement)
  }

  protected onDispose(): void {
    ReactDom.unmountComponentAtNode(this.domElement)
  }

  protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
    if (newValue !== oldValue && propertyPath === 'lists') {
      this.loadPropertyPaneResources()
    }
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription,
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('seeMoreUrl', {
                  label: 'See More URL',
                }),
                PropertyFieldMultiSelect('selectedLocations', {
                  key: 'selectedLocations',
                  label: 'Select Office Locations',
                  options: this._locationOptions,
                  selectedKeys: this.properties.selectedLocations
                }),
                PropertyFieldSitePicker('sites', {
                  label: 'Select sites',
                  initialSites: this.properties.sites,
                  context: this.context,
                  deferredValidationTime: 500,
                  multiSelect: false,
                  onPropertyChange: this.onPropertyPaneFieldChanged,
                  properties: this.properties,
                  key: 'sites'
                }),
                PropertyFieldListPicker('lists', {
                  label: 'Select a list',
                  selectedList: this.properties.lists,
                  includeHidden: false,
                  orderBy: PropertyFieldListPickerOrderBy.Title,
                  disabled: false,
                  onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
                  properties: this.properties,
                  context: this.context,
                  onGetErrorMessage: null,
                  deferredValidationTime: 0,
                  key: 'listPickerFieldId',
                  webAbsoluteUrl: this.properties.sites[0].url
                }),
              ],
            },
          ],
        },
      ],
    }
  }

  protected get dataVersion(): Version {
    return Version.parse('1.0')
  }
}

References: https://pnp.github.io/sp-dev-fx-property-controls/controls/PropertyFieldListPicker/ https://pnp.github.io/sp-dev-fx-property-controls/controls/PropertyFieldMultiSelect/ https://pnp.github.io/sp-dev-fx-property-controls/controls/PropertyFieldSitePicker/

1

There are 1 answers

0
Superintendent UI On

I needed to call this.context.propertyPane.refresh() before I resolve the promise in loadPropertyPaneResources.