One of the key parts of SharePoint Web Parts is the ability to have them configurable using the Web Part properties. This story is still true with client-side Web Parts in the new SharePoint Framework. In this post I will show you one of the more common scenarios; how to populate drop downs (and other fields) in the property pane dynamically. But also show you how what’s wrong with the current implementation.

Client-side Web Part Property Pane basics

The Property Pane in client-side Web Parts are defined by the propertyPaneSettings method. It’s a method that returns an IPropertyPaneSettings object representing the pages, groups and fields. This method is invoked whenever you click to edit the Web Part. The method is “synchronous” meaning that you should return a static set of pages, groups and fields - there’s no option to wait for it to load (SPFx issue #127).

Defining the Dropdown

For a Dropdown we use the PropertyPaneDropdown field and specify for instance it like this, in the propertyPaneSettings method.

PropertyPaneDropdown('listName', {
  label: strings.DescriptionFieldLabel,
  options: this._options
})

In this case the options property references a local variable defined as below:

private _options: IPropertyPaneDropdownOption[];

Loading the data…

But when do we load this data into the local variable. We do have a couple of options to do this, but there really is only one viable option at the moment and that is to use the onInit() method. This method always runs when the Web Part is loaded or is added to a page, and it only runs once and it is the only option we have if we want to load something using a promise and make sure to have it loaded before we have the chance of rendering the property pane. The onInit method returns a Promise and that allows us to actually block the loading of the Web Part until we have the data.

To load data, mock or live, I’ve created a simple list data provider, that you can find in the following Gist. Pay attention to the fact that I have set a delay on the mock loading to 5 seconds, this is to simulate a slow request and show you how bad this current implementation actually is. https://gist.github.com/wictorwilen/0bf866ee4fda2f9611f43ee17b9127d5

Then I implement the onInit method as follows:

public onInit<T>(): Promise<T> {
  let dataService = (this.context.environment.type === EnvironmentType.Test || this.context.environment.type === EnvironmentType.Local) ?
    new MockListsService() :
    new ListsService(this.context);

  this._options = [];

  return new Promise<T>((resolve: (args: T) => void, reject: (error: Error) => void) => {
    dataService.getListNames().then(lists => lists.forEach(list => {
      this._options.push(<IPropertyPaneDropdownOption>{
        text: list,
        key: list
      });
      resolve(undefined);
    }))
  });

First of all I create my data service object (mock or real one). Then I create a new Promise that uses my list service and once the list data is read it populates the local variable holding the data for our dropdown and finally I resolve the promise.

There are some articles and Github repos that uses another approach, they just return a resolved promise and let the call to the external data service run in the background. If that background request takes to long time (the simulated 5 seconds for instance) or fails then the user might have time to open the property pane and see an empty dropdown.

Now when the web part is added to a page or a page a loaded with that web part the onInit method will “block” the web part from rendering until it has the data for the property pane. Well, that’s not good, but that’s how it is at the moment. There is however a possibility that we might be able to use displayMode property of the web part and only do this when we are in edit mode. I have not been able to verify this as the workbench always are in edit mode and none of my tenants actually loads the client side web parts in the modern pages - I’ll get back on this.

Another annoying thing is that since this is blocking the rendering, not even showing a spinning wheel or something, the user experience is quite bad if you have long running calls since nothing is shown (SPFx issue #228).

Summary

I’ve now shown you how to pre-load data for your property pane fields, yes same approach works for other field types than dropdowns as well. But is this the proper way then? Nope, it isn’t but I have great hopes that this will be fixed in a better manner (see linked issues). If not we still have the option of creating completely custom property pane fields that allows us to do basically whatever we want.