---
title: Custom Edit Controls for SharePoint SPFx WebParts
description: The process of creating a custom react component for a SharePoint Framework WebPart that allows for editing an array of objects in the settings.
image: https://blog.hansevision.com/hubfs/code-820275.jpg_large-3.jpg
---

[![HV_BechtleGroup_sRGB_Black_2025 (3)](https://blog.hansevision.com/hs-fs/hubfs/Website%20Weebly%20Bildmaterialien/Logos/HV_BechtleGroup_sRGB_Black_2025%20(3).png?width=225&height=45&name=HV_BechtleGroup_sRGB_Black_2025%20(3).png)](https://www.hansevision.com/) [Blog](https://blog.hansevision.com)

 Lösungen

 Leistungen

[Über uns](https://www.hansevision.com/hansevision.html)

 Karriere

[Kontakt](https://www.hansevision.com/kontakt.html)

[![HV_BechtleGroup_sRGB_Black_2025 (3)](https://blog.hansevision.com/hs-fs/hubfs/Website%20Weebly%20Bildmaterialien/Logos/HV_BechtleGroup_sRGB_Black_2025%20(3).png?width=163&height=32&name=HV_BechtleGroup_sRGB_Black_2025%20(3).png)](https://www.hansevision.com/) [Blog](https://blog.hansevision.com)

 Lösungen

 Leistungen

[Über uns](https://www.hansevision.com/hansevision.html)

 Karriere

Zurück

 5 Minuten Lesezeit  12.06.2019

[SharePoint](https://blog.hansevision.com/tag/sharepoint) [Tutorial](https://blog.hansevision.com/tag/tutorial) [Coding](https://blog.hansevision.com/tag/coding)

<https://twitter.com/intent/tweet?original_referer=https://blog.hansevision.com/custom-edit-controls-for-sharepoint-spfx-webparts?utm_medium=social&utm_source=twitter&url=https://blog.hansevision.com/custom-edit-controls-for-sharepoint-spfx-webparts?utm_medium=social&utm_source=twitter&source=tweetbutton&text=> <http://www.linkedin.com/shareArticle?mini=true&url=https://blog.hansevision.com/custom-edit-controls-for-sharepoint-spfx-webparts?utm_medium=social&utm_source=linkedin>

![Bild von Dan Deaconu](https://blog.hansevision.com/hubfs/Theme%20Development/user-large-solid.svg)

[Dan Deaconu](https://blog.hansevision.com/author/dan-deaconu)

# Custom Edit Controls for SharePoint SPFx WebParts

---

> The edit panel for SPFx WebParts provides a new and consistent way to interact with the users. And although Microsoft has provided plenty of [field types out of the box](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/basics/integrate-with-property-pane) we still need to build our own some times. **But what if we want to do something crazy? Like having an array of objects in our settings?**
> 
> ---

[![](https://blog.hansevision.com/hs-fs/hubfs/Imported_Blog_Media/2019-06-12_16-07-58-1.gif?width=1018&height=740&name=2019-06-12_16-07-58-1.gif)](https://static.hsstatic.net/BlogImporterAssetsUI/ex/missing-image.png)

## The easy part

**Let’s say we store a list of users for a WebPart that shows Contact Persons.** The first thing we would do is create a TypeScript model and extend the .manifest.json file. Something like this:

\\src\\models\\**personProperties.ts**

```
export class PersonProperties {    public guid: string;    public firstName: string;    public lastName: string;     public emailAddress: string;   }   
```

[![](https://blog.hansevision.com/hs-fs/hubfs/Imported_Blog_Media/Capture-1.png?width=367&height=142&name=Capture-1.png)](https://static.hsstatic.net/BlogImporterAssetsUI/ex/missing-image.png)

 

\\src\\webparts\\coolEditFields\\CoolEditFieldsWebPart**.manifest.json**

```
"preconfiguredEntries": [{    "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other    "group": { "default": "Other" },    "title": { "default": "Cool edit fields" },    "description": { "default": "WebPart with cool edit fields" },    "officeFabricIconFontName": "Page",    "properties": {      "contactPersons": [        { "guid": "3646e597-a5b0-448c-8fe4-e1c0ec5199d6", "firstName": "Dan", "lastName": "Deaconu", "emailAddress": "dan.deaconu@email.com" },        { "guid": "09f550b5-42ce-4517-bf9f-eadde70dad7c","firstName": "Willy", "lastName": "Billy", "emailAddress": "willy.billy@email.com" }       ]    }  }]  
```

The second thing to fix up is the interface for the WebPart properties, the interface for the WebPart Field Properties and React.createElement function:

 

 

\\src\\webaparts\\coolEditFields\\components\\ **ICoolEditFieldsProps.ts**

```
export interface ICoolEditFieldsProps {   contactPersons:PersonProperties[];  }
```

Don’t forget to import your model 👍

```
import { PersonProperties } from "../../../models/personProperties";
```

[![](https://blog.hansevision.com/hs-fs/hubfs/Imported_Blog_Media/Capture4.png?width=565&height=165&name=Capture4.png)](https://static.hsstatic.net/BlogImporterAssetsUI/ex/missing-image.png)

 

\\src\\webparts\\coolEditFields\\ **CoolEditFieldsWebPart.ts**

```
export interface ICoolEditFieldsWebPartProps {   contactPersons:PersonProperties[];  }
```

And the **React.createElement** function (in the same file):

```
React.createElement(CoolEditFields, {    contactPersons:this.properties.contactPersons,  });
```

[![](https://blog.hansevision.com/hs-fs/hubfs/Imported_Blog_Media/Capture5.png?width=1032&height=226&name=Capture5.png)](https://static.hsstatic.net/BlogImporterAssetsUI/ex/missing-image.png)

 

💪 TIP you can use the spread operator if the properties are the same:

```
React.createElement(CoolEditFields, { ...this.properties });
```

 

**How can we see the results?**

Let’s update the WebPart to display the properties:

\\src\\webparts\\coolEditFields\\components\\ **CoolEditFields.tsx**

```
export default class CoolEditFields extends React.Component<ICoolEditFieldsProps, {}> {    public render(): React.ReactElement<ICoolEditFieldsProps> {      return (        <div className={styles.coolEditFields}>          <div className={styles.container}>            {this.props.contactPersons.map(person => (              <div className={styles.row}>                <div className={styles.column}>                  <p className={styles.title}>                    {person.firstName} {person.lastName}                  </p>                  {person.emailAddress} <br />                  {person.guid}                  <hr />                </div>              </div>            ))}          </div>        </div>      );    }  }
```

Run **gulp serve** and the WebPart should look something like this:

[![](https://blog.hansevision.com/hs-fs/hubfs/Imported_Blog_Media/Capture-3-1.png?width=849&height=377&name=Capture-3-1.png)](https://static.hsstatic.net/BlogImporterAssetsUI/ex/missing-image.png)

## The fun part

> Settings are there, but how do we edit them? Time to create some components ⚡

Go ahead and create a new folder somewhere in your project. After that you will need the following files:

- personsField.tsx
- iPersonsField.ts
- personsField.module.scss
- persosnsField.module.scss

These files are for our custom React Component

- propertyPanePersonsField.tsx
- iPropertyPanePersonsField.ts
- iPropertyPanePersonsFieldProps.ts
- propertyPanePersonsField.tsx

These files tell our React Component how to talk with SharePoint

 

---

Let’s start with the interface for our React Component. This component will receive from SharePoint the property itself (in our case the array of persons), a callback when something has changed (similar to pushing state up) and a stateKey (required by SPFx, will trigger rerendering)

```
import { PersonProperties } from "../../../models/personProperties";    export interface IPersonsField {     persons: PersonProperties[];     onChanged: (persons:PersonProperties[]) =>void;     stateKey:string;  }
```

[![](https://blog.hansevision.com/hs-fs/hubfs/Imported_Blog_Media/Capture6.png?width=592&height=189&name=Capture6.png)](https://static.hsstatic.net/BlogImporterAssetsUI/ex/missing-image.png)

 

The React Component will render an add button, the persons names with a small edit button and an add/edit modal. I won’t go into the details about React development, but here is the code (you’re gonna have to figure out the CSS on your own 😉)

```
import * as React from "react";  import { ActionButton, IconButton } from "office-ui-fabric-react/lib/Button";  import * as strings from "CoolEditFieldsWebPartStrings";  import { Dialog, DialogType, DialogFooter } from "office-ui-fabric-react/lib/Dialog";  import { DefaultButton } from "office-ui-fabric-react/lib/Button";  import { TextField } from "office-ui-fabric-react/lib/TextField";  import { getGUID } from "@pnp/common";  import { find } from "@microsoft/sp-lodash-subset";  import { IPersonsField } from "./iPersonsField";  import { PersonProperties } from "../../../models/personProperties";  import styles from "./personsField.module.scss";    export interface IPersonsFieldState {     openedModalItemGuid?: string;     persons:PersonProperties[];  }    export default class PersonsField extends React.Component<IPersonsField, IPersonsFieldState> {     constructor(props:IPersonsField) {        super(props);        this.state= {           persons:this.props.persons,        };     }       private onPersonPropertiesChanged (personGuid: string, propertyName: string, value:any) {        var persons=this.state.persons;               for (var column of persons) {          if (column.guid === personGuid) {             column[propertyName] =value;          }        }             this.setState({ persons });        this.props.onChanged(persons);     }       public render():JSX.Element {       var selectedPerson = find(this.state.persons, (person: PersonProperties) => {          return person.guid == this.state.openedModalItemGuid;        });          return (      <div>        <ActionButton            iconProps=            onClick={() => {              var newPersons = this.state.persons;              newPersons.push({                 guid:getGUID(),                 firstName:"John",                 lastName:"Doe",                 emailAddress:"",              });              this.setState({                persons:newPersons,              });              this.props.onChanged(newPersons);          }}>{strings.create}</ActionButton>       {this.state.persons.map(personProperties=> (        <div className={styles.hvColumnSetting} key={personProperties.guid}>           <div className={styles.hvColumnTitle}>              {personProperties.firstName} {personProperties.lastName}           </div>           <IconButtoniconProps= onClick={() => this.setState({ openedModalItemGuid:personProperties.guid })}/>        </div>     ))}     {this.state.openedModalItemGuid ? (        <Dialog hidden={false} onDismiss={() =>this.setState({ openedModalItemGuid:undefined })} minWidth={630} title={strings.modalHeader} type={DialogType.normal}>           <TextField              label={strings.firstName}              value={selectedPerson.firstName}              onChange={(_event, value) =>this.onPersonPropertiesChanged(selectedPerson.guid, "firstName", value)}          />          <TextField             label={strings.lastName}             value={selectedPerson.lastName}             onChange={(_event, value) =>this.onPersonPropertiesChanged(selectedPerson.guid, "lastName", value)}         />         <TextField             label={strings.emailAddress}             value={selectedPerson.emailAddress}             onChange={(_event, value) =>this.onPersonPropertiesChanged(selectedPerson.guid, "emailAddress", value)}         />        <DialogFooter>           <DefaultButtononClick={() =>this.setState({ openedModalItemGuid:undefined })}text={strings.close}/>       </DialogFooter>      </Dialog>     ) : null}    </div>    );   }  }
```

> So little code for so much logic? The magic of modern programming 🧙‍♂️

Let’s tell SharePoint how to talk with our component. Firstly we need the interface for our custom PropertyPane. This includes our data (in this case an array of PersonProperties) and a callback when the data has changed.

\\src\\webparts\\coolEditFields\\**personsField\\iPropertyPanePersonsField.ts**

```
import { PersonProperties } from "../../../models/personProperties";    export interface IPropertyPanePersonsFieldProps {    persons: PersonProperties[];    onPropertyChange: (propertyPath: string, newValue: any) => void;  }  
```

Secondly we need to merge our custom PropertyPane interface with the one from SharePoint

\\src\\webparts\\coolEditFields\\**personsField\\iPropertyPanePersonsFieldInternalProps.ts**

```
import { IPropertyPanePersonsFieldProps } from "./iPropertyPanePersonsFieldProps";  import { IPropertyPaneCustomFieldProps } from "@microsoft/sp-webpart-base";    export interface IPropertyPanePersonsFieldInternalProps extends IPropertyPanePersonsFieldProps, IPropertyPaneCustomFieldProps {}  
```

Lastly we create a new PropertyPane Component. This will merge our component with the properties from SharePoint, take care of rendering and callbacks.

\\src\\webparts\\coolEditFields\\**personsField\\propertyPanePersonsField.ts**

```
import * as React from "react";  import * as ReactDom from "react-dom";  import { IPropertyPaneField, PropertyPaneFieldType } from "@microsoft/sp-webpart-base";  import { IPropertyPanePersonsFieldProps } from "./iPropertyPanePersonsFieldProps";  import { IPropertyPanePersonsFieldInternalProps } from "./iPropertyPanePersonsFieldInternalProps";  import { IPersonsField } from "./iPersonsField";  import { PersonProperties } from "../../../models/personProperties";  import PersonsField from "./personsField";    export class PropertyPanePersonsField implements IPropertyPaneField {    public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;    public targetProperty: string;    public properties: IPropertyPanePersonsFieldInternalProps;    private elem: HTMLElement;      constructor(targetProperty: string, properties: IPropertyPanePersonsFieldProps) {      this.targetProperty = targetProperty;      this.properties = {        key: "personsPropertersSettingsField",        onRender: this.onRender.bind(this),        persons: properties.persons,        onPropertyChange: properties.onPropertyChange,      };    }      public render(): void {      if (!this.elem) {        return;      }        this.onRender(this.elem);    }      private onRender(elem: HTMLElement): void {      if (!this.elem) {        this.elem = elem;      }        const element: React.ReactElement = React.createElement(PersonsField, {        persons: this.properties.persons,        onChanged: this.onChanged.bind(this),        // required to allow the component to be re-rendered by calling this.render() externally        stateKey: new Date().toString(),      });      ReactDom.render(element, elem);    }      private onChanged(value: PersonProperties[]): void {      this.properties.onPropertyChange(this.targetProperty, value);    }  }  
```

> 🎉 Now we can use it  🎉

Head over to the WebPart Component and add the custom component to getPropertyPaneConfiguration()

```
 groups: [              { groupName: "Contact Persons",                 groupFields: [                  new PropertyPanePersonsField("columns", {                    persons: this.properties.contactPersons,                    onPropertyChange: this.updatePersonsSettings.bind(this),                  })] }            ],  
```

[![](https://blog.hansevision.com/hs-fs/hubfs/Imported_Blog_Media/Capture7.png?width=533&height=219&name=Capture7.png)](https://static.hsstatic.net/BlogImporterAssetsUI/ex/missing-image.png)

 

You’re gonna need the update function too 😉

```
private updatePersonsSettings(propertyPath: string, newValue: PersonProperties[]) {      update(this.properties, propertyPath, () => {        return newValue;      });      this.render();    }  
```

> **🚀 gulp serve and check it out 🚀**

 

 

![Bild von Dan Deaconu](https://blog.hansevision.com/hubfs/Theme%20Development/user-large-solid.svg)

[Dan Deaconu](https://blog.hansevision.com/author/dan-deaconu)  Diese Person ist nicht mehr bei der HanseVision tätig. [Alle Artikel des Autors](https://blog.hansevision.com/author/dan-deaconu)

### Ähnliche Blog-Artikel

![](https://blog.hansevision.com/hs-fs/hubfs/Theme%20Development/hansevision-newsletter.png?width=410&name=hansevision-newsletter.png)

### Mit unserem HanseVision Update sind Sie immer gut informiert über alle Themen rund um moderne Zusammenarbeit, Lösungen und Tools, Referenzen und Aktionen.

[![Jetzt zum Newsletter anmelden](https://hubspot-no-cache-eu1-prod.s3.amazonaws.com/cta/default/24882854/c769ef8c-5eed-48ae-ada2-7fc72d656a50.png)](https://hubspot-cta-redirect-eu1-prod.s3.amazonaws.com/cta/redirect/24882854/c769ef8c-5eed-48ae-ada2-7fc72d656a50)

Updates & Aktionen

Versand alle 4-6 Wochen

Trends & aktuelle Entwicklungen

![](https://blog.hansevision.com/hs-fs/hubfs/Theme%20Development/hansevision-newsletter.png?width=250&height=250&name=hansevision-newsletter.png)

[![Jetzt zum Newsletter anmelden](https://hubspot-no-cache-eu1-prod.s3.amazonaws.com/cta/default/24882854/c769ef8c-5eed-48ae-ada2-7fc72d656a50.png)](https://hubspot-cta-redirect-eu1-prod.s3.amazonaws.com/cta/redirect/24882854/c769ef8c-5eed-48ae-ada2-7fc72d656a50)

<https://www.youtube.com/channel/UCZQCgsNj4irQw3Ozhqf3YaA?sub_confirmation=1> <https://www.linkedin.com/company/5723266>

[Datenschutzerklärung](https://www.hansevision.com/datenschutzerklaerung.html)

[Impressum](https://www.hansevision.com/impressum-688471.html)

[AGB](https://www.hansevision.com/uploads/4/1/9/3/41931735/hansevision_agb_der_hansevision_gmbh_stand_31.03.2020.pdf)

[Telefon: +49 (40) 2880 7590 - 0](tel:+4940288075900)

 Adresse: Atlantic Haus, Bernhard-Nocht-Strasse 113, 20359 Hamburg

[Cookie-Richtlinien](https://www.hansevision.com/cookie-richtlinie.html)

 Cookie-Einstellungen

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Dan Deaconu",
    "url" : "https://blog.hansevision.com/author/dan-deaconu"
  },
  "dateModified" : "2024-03-19T10:46:45.298Z",
  "datePublished" : "2019-06-12T22:00:00.000Z",
  "headline" : "Custom Edit Controls for SharePoint SPFx WebParts",
  "image" : [ "https://blog.hansevision.com/hubfs/code-820275.jpg_large-3.jpg" ],
  "mainEntityOfPage" : {
    "@id" : "https://blog.hansevision.com/custom-edit-controls-for-sharepoint-spfx-webparts",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://blog.hansevision.com/hubfs/HanseVision_sRGB_Black.png"
    },
    "name" : "HanseVision GmbH"
  }
}
```