Create a SharePoint App with React: Save to SharePoint with pnp/sp

Please NOTE: This blog series is currently a work in progress and subject to change.

This is part 5 in a series about creating a React App, using the Microsoft PNPJS library and Ui-Fabric in SharePoint.

Disclaimer


WebDev By The Bay is not affiliated with nor is this post endorsed by any company mentioned in this post. 

All opinions belong to the author of this post.

All Copyrights and Trademarks belong to their respective owners.

Full disclosure!

I am not a Software Engineer, I’m a Web Developer of almost 20 years. In my day job, for the past 6 years, I have been tasked with developing solutions in SharePoint.

This series is my journey in learning not only SharePoint but various other technologies.

Please Note: The only SharePoint App Model that Microsoft recommends is the SharePoint Framework aka. SPFx.

We will create some custom code but most of the code will be from the library examples to make learning the technology easier, at least for me.

This series is not about showing you how to code the various pieces but rather how you can piece the pieces together to make an application.

The basic setup of the React App is based on react-starter by Ted Pattison.

I will be adding how to use the PNPJS Library to surface SharePoint list data, modifying webpack.config and providing some tips on using the FluentUi.

The app will use the Microsoft PNPJS library to surface the data in the backing SharePoint list data and Microsoft Power Automate to do the Approval workflows.

Please note the following:
1.The PNPJS library may be out of date at the time you read this post.
Please be sure to check for updates.
2. You will need to upload the bundle js file and index.aspx to SharePoint before you can debug communication with SharePoint List data.

App Purpose

Dashboard app to help manage Long Term Parking Requests and Approvals.

Please NOTE: PNPJS v2 was published Dec 20, 2019.
We will use v2 in this tutorial.
https://pnp.github.io/pnpjs/transition-guide/

Stop the project if it is still running. In part 4 we modified the form and used PnPJs to surface Campuses from a SharePoint list. We also surfaced a list of buildings from SharePoint that are related to our chosen campus. The following screenshot is the latest version of the form.

Request Form with Parking Input fields
Request Form with Parking Input fields

Before we hook up our Submit Parking Request button to save the form data to our Parking Request list in SharePoint, let’s make some changes to our form and backing Parking Request list in SharePoint.

In the ParkingRequest list remove the following columns:

  • Vehicle Location – Building
  • Vehicle Location – Details

Add the following columns:

ContactNameSingle line of text
ContactEmailSingle line of text
ContactPhoneSingle line of text
LevelSingle line of text

On our Request Form we need to add a check box next to the Parking Policies link, to allow the requestor to acknowledge they have read the Parking Policy.

<div className="ms-Grid-col ms-sm6 ms-md4 ms-lg4">
    <Stack horizontal tokens={{ childrenGap: 50 }} 
     styles={{ root: { width: 850 } }}>
      <Link className="policyLink" href="#/policies">
        <PolicyIcon />
     <strong> Parking Policies</strong></Link>
     <Toggle id="IhaveReadParkingPolicy" 
       onText="I have read and understand the Parking Policy."  offText="Please make sure you have read and understand the Parking Policy!"/>
    </Stack>
</div>

WebDev By The Bay - Parking Request Toggle Button
WebDev By The Bay – Parking Request Toggle Button

Connect to SharePoint

There are several ways to connect your code to SharePoint.

The direct way works for several cases but will cause an issue when trying to add data to a SharePoint list.

Direct method: const web = Web(“{YOUR SHAREPOINT URL}”);

If you try

const iar: IItemAddResult = await web.lists.getByTitle(“ParkingRequest”).items.add({

Error: “Unexpected Token < at position 0

This means that the function is trying to return the html page it was called from instead of the data in JSON format. The < at position 0 refers to the first line of the html page which is the opening tag in <!DOCTYPE html>

Instead we need to use

sp.setup({
  sp: {
    headers: {
      Accept: "application/json;odata=verbose",
    },
    baseUrl: "{YOUR SHAREPOINT URL}"
  },
});

And then setup our function like:
const iar: IItemAddResult = await sp.web.lists.getByTitle("ParkingRequest").items.add({

Add a User function

Let’s create a user function in it’s own file so that we can call it from anywhere in our app.

This code will surface the current user. If you have not already done so, create a new folder in the src directory and name it utility.

In the utility directory create a new file named user.tsx

import * as React from 'react';
import { useState } from 'react';

import { Web } from "@pnp/sp/presets/all";

export function appUser()
{
    const [currentUser,_setCUser] = useState('');
    const web = Web("{YOUR SHAREPOINT URL}");

   React.useEffect(() => {
      async function getCUser() {
          try {
          const CUser = await web.currentUser();
          console.log(CUser.UserPrincipalName);
          _setCUser(CUser.UserPrincipalName)
          } catch (error) {}
      }
      getCUser();
    },[]);

    return currentUser;
}

UserPrincipalName is usually the email address.

In requestForm.tsx import user.tsx. Also make sure that you have imported sp and IItemAddResult

import { appUser } from '../../../utility/user';
import { sp } from "@pnp/sp/presets/all";
import { IItemAddResult } from "@pnp/sp/items";

Modify the form!

We need to make several changes to the form, add ids to our fields and controls and connect the controls their functions.

But first let’s setup the state for the form.

Add the following right after sp.setup

// FORM STATE
const currentUser = appUser();
const [state, dispatch] = useReducer(reducer,[]);
const [isDisabled, toggleDisable] = useState(true);
let [selectedBuilding, setBuilding] = useState("");
let [selectedLevel, setLevel] = useState("");
let [selectedCampus, setCampus] = useState("");

Add id’s to the form controls and connect them to their callbacks.

<form className="requestForm" name="parkingRequestForm">
    <div className="container">
      <div className="ms-Grid" dir="ltr">
          <div className="ms-Grid-row">

              <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg4">
                    <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 850 } }}>
                    <Link className="policyLink" href="#/policies"><PolicyIcon /><strong>Parking Policies</strong></Link>
                        <Toggle id="IhaveReadParkingPolicy" onText="I have read and understand the Parking Policy." offText="Please make sure you have read and understand the Parking Policy!" onChange={() => toggleDisable(!isDisabled)}/>
                    </Stack>
                </div>
              <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg12">
                  <Label><strong>All Fields are Required!</strong></Label>
                  <MessageBar>
                        Mobile App version is available from the Power Apps Viewer. The viewer can be downloaded from the App Store link on your mobile device.
                    </MessageBar>
              </div>

          </div>
          <div className="ms-Grid-row row-spacer">
                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg2">
                    <TextField id="empID" label="EmployeeID" disabled={isDisabled}/>
                </div>
          </div>
           <div className="ms-Grid-row row-spacer">
                <Label><strong>Manager Information - This will eventually come from Azure Active Directory</strong></Label>
                <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
                    <TextField id="managerName" label="Manager Name" disabled={isDisabled}/>
                    <MaskedTextField id="managerOfficePhone" label="Phone" mask="(999) 999 - 9999" disabled={isDisabled}/>
                    <TextField id="managerEmailAddress" label="Email" disabled={isDisabled}/>
                </Stack>

            </div>

            <div className="ms-Grid-row row-spacer">
                <Label><strong>Emergency Contact Information</strong></Label>
                <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
                    <TextField id="contactName" label="Contact Name" disabled={isDisabled}/>
                    <MaskedTextField  id="contactPhone" label="Phone" mask="(999) 999 - 9999" disabled={isDisabled}/>
                    <TextField  id="contactEmailAddress" label="Email" disabled={isDisabled}/>
                </Stack>

            </div>
          <div className="ms-Grid-row row-spacer">

                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg6">
                  <Label>Start Date</Label>
                    <DatePicker id="startDate"
                    onSelectDate={_onSelectStartDate}
                    disabled={isDisabled}
                    className={controlClass.control}
                    strings={DayPickerStrings}
                    placeholder="Start Date"
                    ariaLabel="Start Date"
                    />
                </div>
                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg6">
                    <Label>Return Date</Label>
                    <DatePicker
                    id="returnDate"
                    onSelectDate={_onSelectReturnDate}
                    disabled={isDisabled}
                    className={controlClass.control}
                    strings={DayPickerStrings}
                    placeholder="Return Date"
                    ariaLabel="Return Date"
                    />
                </div>
              </div>
          </div>
           <div className="ms-Grid-row row-spacer">
                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg12">
                    <Label><strong>Vehicle Information</strong></Label>
                <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
                    <TextField id="make" label="Make" disabled={isDisabled}/>
                    <TextField id="model" label="Model" disabled={isDisabled}/>
                    <TextField id="year" label="Year" disabled={isDisabled}/>
                    <TextField id="color" label="Color" disabled={isDisabled}/>
                    <TextField id="licensePlateNumber" label="License Plate" disabled={isDisabled}/>
                </Stack>
                <TextField id="distinquishingFeatures" label="Distinguishing features and or marks to assist Security personnel in identifying your vehicle." multiline rows={3} disabled={isDisabled}/>
                </div>
          </div>
          <div className="ms-Grid-row row-spacer">
                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg12">
                    <Label><strong>Parking Information</strong></Label>
                    <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
                        <Dropdown
                        id="campus"
                        placeholder="Select a Campus"
                        disabled={isDisabled}
                        label="Campus"
                        defaultSelectedKey=""
                        options={state.campuses}
                        onChange={_getBuildings}/>

                        <Dropdown
                        id="building"
                        placeholder="Select a Building"
                        disabled={isDisabled}
                        label="Building"
                        defaultSelectedKey=""
                        options={state.buildings}
                        onChange={_setBuilding} />

                         <Dropdown
                         id="level" placeholder="Level"
                         disabled={isDisabled}
                         label="Level"
                         defaultSelectedKey="Ground"
                         options={levels}
                         onChange={_setLevel} />
                    </Stack>
                </div>
          </div>
          <div className="ms-Grid-row row-spacer">
              <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg12">
                <TextField id="justification" label="Justification" multiline rows={4} disabled={isDisabled} />
            </div>
          </div>



            <div className="ms-Grid-row align-right">
                <PrimaryButton className="deepSkyBlue" text="Submit Parking Request" onClick={_saveParkingRequest}  allowDisabledFocus disabled={isDisabled}/>
            </div>

      </div>
      </form>

Add the Callbacks


    // DATEPICKERS
    function _onSelectStartDate(date: Date | null | undefined): void  {
        console.log("STARTDATE: " + date);
        selectedStartDate = date;
    }

     function _onSelectReturnDate(date: Date | null | undefined): void  {
         console.log("ENDDATE: " + date);
        selectedEndDate = date;
     }

      // SAVE - we need to wrap everything in an async function

     async function _saveParkingRequest(): Promise<void> {
        // We need to reference the form in order to gain access to it's controls and the controls values.
        const form = document.forms['parkingRequestForm'];
         // add an item to the list
         // Format - {SharePoint List Field Name}:{Value}
        const iar: IItemAddResult = await sp.web.lists.getByTitle("ParkingRequest").items.add({
        Title: "New Parking Request",
        ReadParkingPolicy: true,
        Justification: form.elements.justification.value,
        EmployeeId: currentUser,
        LicensePlate: form.elements.licensePlateNumber.value,
        Make: form.elements.make.value,
        Color: form.elements.color.value,
        Model: form.elements.model.value,
        Year: form.elements.year.value,
        StartDate: selectedStartDate,
        EndDate: selectedEndDate,
        ContactName: form.elements.contactName.value,
        ContactNumber: form.elements.contactPhone.value,
        ContactEmail: form.elements.contactEmailAddress.value,
        Level: selectedLevel,
        Campus: selectedCampus ,
        Building: selectedBuilding,
        Manager: form.elements.managerName.value,
        ManagerPhone: form.elements.managerOfficePhone.value,
        ManagerEmail: form.elements.managerEmailAddress.value
        });
        console.log("iar: " + iar);
     }

Completed code:

import * as React from 'react';
import { useState, useContext } from 'react';


// import { IWeb, Web } from '@pnp/sp/webs';
import { DatePicker, DayOfWeek, Dropdown, IDatePickerStrings, IDropdownOption, Label, Link, MaskedTextField, mergeStyles, mergeStyleSets, MessageBar, PrimaryButton, TextField, Toggle, DefaultButton } from 'office-ui-fabric-react';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { Stack } from 'office-ui-fabric-react/lib/Stack';

import { useEffect, useReducer } from 'react';
import IBuilding from "../../../models/IBuilding";
import ICampus from '../../../models/ICampus';
import './requestForm.scss';


import { appUser } from '../../../utility/user';


import { sp } from "@pnp/sp/presets/all";
import { IItemAddResult } from "@pnp/sp/items";

const PolicyIcon = () => <Icon iconName="DocumentSet" className="ms-IconExample" />;

const iconClass = mergeStyles({
  fontSize: 50,
  height: 50,
  width: 50,
  margin: '0 25px'
});

const classNames = mergeStyleSets({
  deepSkyBlue: [{ color: 'deepskyblue' }, iconClass],
  greenYellow: [{ color: 'greenyellow' }, iconClass],
  salmon: [{ color: 'salmon' }, iconClass]
});

const DayPickerStrings: IDatePickerStrings = {
  months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],

  shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],

  days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],

  shortDays: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],

  goToToday: 'Go to today',
  prevMonthAriaLabel: 'Go to previous month',
  nextMonthAriaLabel: 'Go to next month',
  prevYearAriaLabel: 'Go to previous year',
  nextYearAriaLabel: 'Go to next year',
  closeButtonAriaLabel: 'Close date picker'
};

const controlClass = mergeStyleSets({
  control: {
    margin: '0 0 15px 0',
    maxWidth: '300px'
  }
});

export interface IDatePickerState {
  firstDayOfWeek?: DayOfWeek;
}

export interface ICompanyLocationState {
    campuses: ICampus[],
    buildings: IBuilding[],
    selectedCampus: string
}

export interface ICampuses {
  Id: number;
  Title: string;
}

export interface IBuildings {
  Id: number;
  Title: string;
  Street: string;
  City: string;
  PostalCode: string;
}

const levels: IDropdownOption[] = [
    {key: 'ground', text: 'Ground'},
    {key: '1', text: '1'},
    {key: '2', text: '2'},
    {key: '3', text: '3'},
    {key: '4', text: '4'},
    {key: '5', text: '5'}
];

let selectedEndDate:Date;
let selectedStartDate:Date;

function ParkingRequestForm () {

sp.setup({
  sp: {
    headers: {
      Accept: "application/json;odata=verbose",
    },
    baseUrl: "{YOUR SHAREPOINT URL}"
  },
});

// FORM STATE
const currentUser = appUser();
const [state, dispatch] = useReducer(reducer,[]);
const [isDisabled, toggleDisable] = useState(true);
let [selectedBuilding, setBuilding] = useState("");
let [selectedLevel, setLevel] = useState("");
let [selectedCampus, setCampus] = useState("");

useEffect(() => {
      _getCampuses();
      return () => {
        console.log('Calling get Campuses');
      }
},[]);



return (
 <form className="requestForm" name="parkingRequestForm">
    <div className="container">
      <div className="ms-Grid" dir="ltr">
          <div className="ms-Grid-row">

              <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg4">
                    <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 850 } }}>
                    <Link className="policyLink" href="#/policies"><PolicyIcon /><strong>Parking Policies</strong></Link>
                        <Toggle id="IhaveReadParkingPolicy" onText="I have read and understand the Parking Policy." offText="Please make sure you have read and understand the Parking Policy!" onChange={() => toggleDisable(!isDisabled)}/>
                    </Stack>
                </div>
              <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg12">
                  <Label><strong>All Fields are Required!</strong></Label>
                  <MessageBar>
                        Mobile App version is available from the Power Apps Viewer. The viewer can be downloaded from the App Store link on your mobile device.
                    </MessageBar>
              </div>

          </div>
          <div className="ms-Grid-row row-spacer">
                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg2">
                    <TextField id="empID" label="EmployeeID" disabled={isDisabled}/>
                </div>
          </div>
           <div className="ms-Grid-row row-spacer">
                <Label><strong>Manager Information - This will eventually come from Azure Active Directory</strong></Label>
                <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
                    <TextField id="managerName" label="Manager Name" disabled={isDisabled}/>
                    <MaskedTextField id="managerOfficePhone" label="Phone" mask="(999) 999 - 9999" disabled={isDisabled}/>
                    <TextField id="managerEmailAddress" label="Email" disabled={isDisabled}/>
                </Stack>

            </div>

            <div className="ms-Grid-row row-spacer">
                <Label><strong>Emergency Contact Information</strong></Label>
                <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
                    <TextField id="contactName" label="Contact Name" disabled={isDisabled}/>
                    <MaskedTextField  id="contactPhone" label="Phone" mask="(999) 999 - 9999" disabled={isDisabled}/>
                    <TextField  id="contactEmailAddress" label="Email" disabled={isDisabled}/>
                </Stack>

            </div>
          <div className="ms-Grid-row row-spacer">

                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg6">
                  <Label>Start Date</Label>
                    <DatePicker id="startDate"
                    onSelectDate={_onSelectStartDate}
                    disabled={isDisabled}
                    className={controlClass.control}
                    strings={DayPickerStrings}
                    placeholder="Start Date"
                    ariaLabel="Start Date"
                    />
                </div>
                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg6">
                    <Label>Return Date</Label>
                    <DatePicker
                    id="returnDate"
                    onSelectDate={_onSelectReturnDate}
                    disabled={isDisabled}
                    className={controlClass.control}
                    strings={DayPickerStrings}
                    placeholder="Return Date"
                    ariaLabel="Return Date"
                    />
                </div>
              </div>
          </div>
           <div className="ms-Grid-row row-spacer">
                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg12">
                    <Label><strong>Vehicle Information</strong></Label>
                <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
                    <TextField id="make" label="Make" disabled={isDisabled}/>
                    <TextField id="model" label="Model" disabled={isDisabled}/>
                    <TextField id="year" label="Year" disabled={isDisabled}/>
                    <TextField id="color" label="Color" disabled={isDisabled}/>
                    <TextField id="licensePlateNumber" label="License Plate" disabled={isDisabled}/>
                </Stack>
                <TextField id="distinquishingFeatures" label="Distinguishing features and or marks to assist Security personnel in identifying your vehicle." multiline rows={3} disabled={isDisabled}/>
                </div>
          </div>
          <div className="ms-Grid-row row-spacer">
                <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg12">
                    <Label><strong>Parking Information</strong></Label>
                    <Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
                        <Dropdown
                        id="campus"
                        placeholder="Select a Campus"
                        disabled={isDisabled}
                        label="Campus"
                        defaultSelectedKey=""
                        options={state.campuses}
                        onChange={_getBuildings}/>

                        <Dropdown
                        id="building"
                        placeholder="Select a Building"
                        disabled={isDisabled}
                        label="Building"
                        defaultSelectedKey=""
                        options={state.buildings}
                        onChange={_setBuilding} />

                         <Dropdown
                         id="level" placeholder="Level"
                         disabled={isDisabled}
                         label="Level"
                         defaultSelectedKey="Ground"
                         options={levels}
                         onChange={_setLevel} />
                    </Stack>
                </div>
          </div>
          <div className="ms-Grid-row row-spacer">
              <div className="ms-Grid-col ms-sm6 ms-md4 ms-lg12">
                <TextField id="justification" label="Justification" multiline rows={4} disabled={isDisabled} />
            </div>
          </div>



            <div className="ms-Grid-row align-right">
                <PrimaryButton className="deepSkyBlue" text="Submit Parking Request" onClick={_saveParkingRequest}  allowDisabledFocus disabled={isDisabled}/>
            </div>

      </div>
      </form>


    );


    // CAMPUSES
    async function _getCampuses(): Promise<void> {

    await sp.web.lists
        .getByTitle("Campus")
        .items
        .select(
            "Id",
            "Title"
        )
        .getAll()
        .then(response => {
            const cmps = response.map(desc => {
                return {
                    ...desc
                };
            });

            dispatch({
                type: "setCampuses",
                data: cmps
            });
        })
    }

    //BUILDINGS
    async function _getBuildings(event: React.FormEvent<HTMLDivElement>, campus: IDropdownOption): Promise<void> {
      setCampus(selectedCampus = campus.text);
    await sp.web.lists
        .getByTitle("Buildings")
        .items.select(
            "Id",
            "Title",
            "Campus"
        )
        .filter("Campus eq %27" + campus.text + "%27")
        .getAll()
        .then(response => {
            const bldgs = response.map(desc => {
                return {
                    ...desc
                };
            });

            dispatch({
                type: "setBuildings",
                data: bldgs
            });
        })
    }

    // FUNCTIONS
    async function _setBuilding(event: React.FormEvent<HTMLDivElement>, building: IDropdownOption): Promise<void> {
        setBuilding(selectedBuilding = building.text);
    }

    async function _setLevel(event: React.FormEvent<HTMLDivElement>, level: IDropdownOption): Promise<void> {
        setLevel(selectedLevel = level.text);
    }

    // DATEPICKER
    function _onSelectStartDate(date: Date | null | undefined): void  {
        console.log("STARTDATE: " + date);
        selectedStartDate = date;
    }

     function _onSelectReturnDate(date: Date | null | undefined): void  {
         console.log("ENDDATE: " + date);
        selectedEndDate = date;
     }

      // SAVE
     async function _saveParkingRequest(): Promise<void> {
        // We need to reference the form in order to gain access to it's controls and the controls values.
        const form = document.forms['parkingRequestForm'];
         // add an item to the list
         // Format - {SharePoint List Field Name}:{Value}
        const iar: IItemAddResult = await sp.web.lists.getByTitle("ParkingRequest").items.add({
        Title: "New Parking Request",
        ReadParkingPolicy: true,
        Justification: form.elements.justification.value,
        EmployeeId: currentUser,
        LicensePlate: form.elements.licensePlateNumber.value,
        Make: form.elements.make.value,
        Color: form.elements.color.value,
        Model: form.elements.model.value,
        Year: form.elements.year.value,
        StartDate: selectedStartDate,
        EndDate: selectedEndDate,
        ContactName: form.elements.contactName.value,
        ContactNumber: form.elements.contactPhone.value,
        ContactEmail: form.elements.contactEmailAddress.value,
        Level: selectedLevel,
        Campus: selectedCampus ,
        Building: selectedBuilding,
        Manager: form.elements.managerName.value,
        ManagerPhone: form.elements.managerOfficePhone.value,
        ManagerEmail: form.elements.managerEmailAddress.value
        });
        console.log("iar: " + iar);
     }

  };

export default ParkingRequestForm;

function reducer(state, action) {
    switch(action.type) {
        case "setCampuses": {
            // We need to rename the keys: Id to key and Title to text
           return { campuses: action.data.map(function(cm) {
                cm['key'] = cm['Id'];
                delete cm['Id'];
                cm['text'] = cm['Title'];
                delete cm['Title'];
                return cm;
            })};
        }
         case "setBuildings": {
            // We need to rename the keys: Id to key and Title to text and keep the current state
           return {...state, buildings: action.data.map(function(bldg) {
                bldg['key'] = bldg['Id'];
                delete bldg['Id'];
                bldg['text'] = bldg['Title'];
                delete bldg['Title'];
                return bldg;
            })};
         }

         case "enableFormControls": {
             return {...state, ...{isDisabled: false}};
         }

         case "disableFormControls": {
             return {...state, ...{isDisabled: true}};
         }

        default:
            return state;
    }


}

Now all the form fields and controls will be disabled by default until the user selects the toggle button and we can save records to our SharePoint list.

References

Register your app with Azure Active Directory
Overview