Don't use PropertyPaneTextField with numeric web part properties

When building SharePoint Framework client-side web parts, don't use PropertyPaneTextField to manage values of numeric web part properties. Here is why.

Configure with ease

What distinguishes web parts from other SharePoint customizations is how easily you can configure them to your specific needs. Using web parts, you can compose pages and have the same web part display different information depending on its settings. The most common examples of web parts' flexibility are the standard content search web part and its predecessor, the content query web part, both capable of showing specific content based on the particular configuration.

You can manage web part's configuration through the web part property pane. This is the case both for classic web parts, which you have been using for years, as well as new client-side web parts that you can build on the SharePoint Framework.

When defining web part properties, you can have them store different value types like strings, numbers, booleans or even custom objects. For each value type, there are different property pane controls that you can use to manage the property value. String properties are for example best managed using the PropertyPaneTextField control, while boolean properties are the easiest to manage using the PropertyPaneToggle control.

PropertyPaneToggle control used to manage the value of a boolean web part property

Generally, it's clear which control to use for which value type. Not so much when working with numbers.

The trouble with numeric web part properties

Depending on the web part that you are building, you could have a numeric property that falls into a specific range (like how many doors a car has) or an arbitrary number (like age). For the first kind of number, you would use the standard PropertyPaneSlider control, that lets users pick a number from the given range. To let users enter any number, you might want to use the PropertyPaneTextField control. And this is when the problems begin.

Choosing a number from a predefined range using the PropertyPaneSlider control

SharePoint Framework allows you to build web parts using TypeScript. The biggest advantage of using TypeScript are its type safety capabilities that help you find any inconsistencies in your code already during development, rather than on runtime.

When defining numeric web part properties in SharePoint Framework client-side web parts, you first define them in the web part's properties interface:

export interface IAgeIn10YearsWebPartProps {  
  age: number;
}

If you want to, you can provide a default value for your property in the web part manifest:

{
  "$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",

  "id": "c1a36be4-6482-489e-867f-9a207876d329",
  "alias": "AgeIn10YearsWebPart",
  "componentType": "WebPart",
  "version": "*", // The "*" signifies that the version should be taken from the package.json
  "manifestVersion": 2,

  /**
   * This property should only be set to true if it is certain that the webpart does not
   *  allow arbitrary scripts to be called
   */
  "safeWithCustomScriptDisabled": false,

  "preconfiguredEntries": [{
    "groupId": "c1a36be4-6482-489e-867f-9a207876d329",
    "group": { "default": "Under Development" },
    "title": { "default": "Age in 10 years" },
    "description": { "default": "Tells you your age in 10 years" },
    "officeFabricIconFontName": "Page",
    "properties": {
      "age": 21
    }
  }]
}

To let users specify the age, you would add the reference to the age property in the web part property pane:

import {  
  PropertyPaneTextField
} from '@microsoft/sp-webpart-base';

// ...

export default class AgeIn10YearsWebPart extends BaseClientSideWebPart<IAgeIn10YearsWebPartProps> {  
  // ...

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('age', {
                  label: strings.AgeFieldLabel
                })
              ]
            }
          ]
        }
      ]
    };
  }
}

Finally, you would add the last part where you calculate and display the age of the person in 10 years:

export default class AgeIn10YearsWebPart extends BaseClientSideWebPart<IAgeIn10YearsWebPartProps> {

  public render(): void {
    const ageIn10Years: number = this.properties.age + 10;

    this.domElement.innerHTML = `
      <div class="${styles.ageIn10Years}">
        In 10 years you will be ${ageIn10Years}
      </div>`;
  }
}

When you first run the web part, it uses the default value from the manifest and everything seems just fine.

Calculating numbers with the default value of a numeric web part property works as expected

But look what happens, when you change the age in web part properties:

Incorrectly calculated age with the value provided in the web part property

I bet, you didn't expect to become that old in your lifetime, not to mention in 10 years! So what's wrong?

One error TypeScript won't save you from

When defining the numeric age property in your web part, you've done everything right: you specified that it was a number and provided a default numeric value. What is incorrect, at least in this case, is the fact, that the PropertyPaneTextField control returns a string which on runtime is assigned to the specified web part property. Of course this assignment is done implicitly based on the name of the web part property rather than a reference to the object. This is why the error is never caught by TypeScript and can be seen only after adding the web part to the page, where you end up adding a number to a string.

Not only does TypeScript let this inconsistency slip undetected, but it also makes it very hard for you to fix. Because you declared your property to be a number, you cannot use the parseInt function, to convert it to a number. After all, parseInt requires a string as a parameter, not a number.

There are three ways in which you can solve this problem.

Web part property managed with the PropertyPaneTextField must be a string

First of all, you can choose to change the type of your age property to string. This will make it harder to use it in calculations, but at least your code will work correctly. Before using the specified value for calculations you would escape it and try to parse as a number:

// ...

export default class AgeIn10YearsWebPart extends BaseClientSideWebPart<IAgeIn10YearsWebPartProps> {

  public render(): void {
    let ageIn10Years: number = parseInt(escape(this.properties.age));
    if (isNaN(ageIn10Years)) {
      ageIn10Years = 21;
    }
    ageIn10Years += 10;

    this.domElement.innerHTML = `
      <div class="${styles.ageIn10Years}">
        In 10 years you will be ${ageIn10Years}
      </div>`;
  }
  // ...
}

Because you're dealing with a string, you also must consider, that the specified value is not a number. You can do this using the isNaN function.

Use the PropertyPaneSlider control to manage the property value

Another choice that you have, is to use the PropertyPaneSlider control to manage the value of the web part. That control doesn't allow arbitrary values. Instead, using a fixed UI, it allows users to select a number from the predefined range. The number selected by the users is returned as a number which prevents you from writing any additional code.

Selecting the age using the PropertyPaneSlider control

Using the PropertyPaneSlider might not always be suitable for your scenario. Already when selecting the age, you might doubt if it's convenient to pick the number from such a wide range using a slider and what the maximum value should be.

Build custom property pane control

If you must let users enter an arbitrary number, then another choice that you have, is to build a custom property pane control similar to the standard PropertyPaneTextField but then suitable for handling numbers. It would obviously be significantly more effort to do than to handle strings, but once done, it would help you keep your web part's code clean, and you could easily reuse it in other projects as well.

Summary

When building SharePoint Framework client-side web parts beware of using PropertyPaneTextField with numeric web part properties. The value assigned internally to the web part property by PropertyPaneTextField is a string, which can lead to incorrect behavior visible only on runtime. Instead, either change your property to string and extract the number from it or use another property pane control that returns the selected value as a number, such as PropertyPaneSlider.

Want to catch this and other errors in your SharePoint Framework solutions easily? Check out SPCAF.

Comments

comments powered by Disqus