reactjssharepointweb-partsspfxpnp-js

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'web') when using sp pnp v3


I am developing a webpart for my sharepoint tenant. When I test my solution on my workbench everything works well. But when I put it on my sharepoint online I get the error described in the description.

Here is my package :

  "packages": {
    "": {
      "name": "gallery_photo",
      "version": "0.0.1",
      "dependencies": {
        "@fluentui/react": "^8.104.6",
        "@fluentui/react-components": "^9.11.1",
        "@fluentui/react-hooks": "^8.6.15",
        "@microsoft/sp-core-library": "1.16.1",
        "@microsoft/sp-lodash-subset": "1.16.1",
        "@microsoft/sp-odata-types": "^1.16.1",
        "@microsoft/sp-office-ui-fabric-core": "1.16.1",
        "@microsoft/sp-property-pane": "1.16.1",
        "@microsoft/sp-webpart-base": "1.16.1",
        "@pnp/graph": "^3.11.0",
        "@pnp/logging": "^3.11.0",
        "@pnp/pnpjs": "^2.15.0",
        "@pnp/sp": "^3.11.0",
        "@pnp/spfx-controls-react": "3.12.0",
        "@pnp/spfx-property-controls": "3.11.0",
        "@reach/dialog": "^0.18.0",
        "@uifabric/utilities": "^7.38.2",
        "office-ui-fabric-react": "^7.199.1",
        "react": "17.0.1",
        "react-dom": "17.0.1",
        "tslib": "2.3.1",
        "uuid": "^9.0.0"
      },
      "devDependencies": {
        "@fullhuman/postcss-purgecss": "^5.0.0",
        "@microsoft/eslint-config-spfx": "1.16.1",
        "@microsoft/eslint-plugin-spfx": "1.16.1",
        "@microsoft/rush-stack-compiler-4.5": "0.2.2",
        "@microsoft/sp-build-web": "1.16.1",
        "@microsoft/sp-module-interfaces": "1.16.1",
        "@rushstack/eslint-config": "2.5.1",
        "@types/react": "17.0.45",
        "@types/react-dom": "17.0.17",
        "@types/webpack-env": "~1.15.2",
        "@typescript-eslint/eslint-plugin": "^5.0.0",
        "@typescript-eslint/parser": "^5.0.0",
        "ajv": "^6.12.5",
        "autoprefixer": "^9.8.6",
        "eslint": "^8.7.0",
        "eslint-plugin-react-hooks": "4.3.0",
        "gulp": "4.0.2",
        "gulp-postcss": "^9.0.1",
        "postcss": "^8.4.21",
        "postcss-cli": "^10.1.0",
        "postcss-import": "^15.1.0",
        "tailwindcss": "^1.9.6",
        "typescript": "4.5.5"
      },
      "engines": {
        "node": ">=16.13.0 <17.0.0"
      }
    },

Here is my code :

import * as React from "react";
import * as ReactDom from "react-dom";
import { Version } from "@microsoft/sp-core-library";
import {
  IPropertyPaneConfiguration,
  PropertyPaneDropdown,
  IPropertyPaneDropdownOption,
  PropertyPaneChoiceGroup,
} from "@microsoft/sp-property-pane";

import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { IReadonlyTheme } from "@microsoft/sp-component-base";
import "@pnp/sp/folders";
import * as strings from "CascadingWebPartStrings";
import Cascading from "./components/Cascading";
import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http";
import "@pnp/sp/fields";
import { getSP } from "../../FilesConfig";
import { SPFI } from "@pnp/sp";
import { IDateTimeFieldValue } from "@pnp/spfx-property-controls/lib/PropertyFieldDateTimePicker";

export interface ICascadingWebPartProps {
  datetime: IDateTimeFieldValue;
  description: string;
  listName: string;
  folderName: string;
  metaData: string;
  Order: string;
}

export default class CascadingWebPart extends BaseClientSideWebPart<ICascadingWebPartProps> {
  private _isDarkTheme: boolean = false;
  private _environmentMessage: Promise<string>;
  private lists: IPropertyPaneDropdownOption[];
  private listsDropdownDisabled: boolean = true;
  private folders: IPropertyPaneDropdownOption[];
  private foldersDropdownDisabled: boolean = true;
  private metaData: IPropertyPaneDropdownOption[] = [
    { key: "Title", text: "Titre" },
    { key: "TimeCreated", text: "Date de création" },
    { key: "TimeLastModified", text: "Date de modification" },
  ];
  private _sp: SPFI;

  public render(): void {
    const Element = (): React.ReactElement => {
      return (
        <Cascading
          metaData={this.properties.metaData}
          description={this.properties.description}
          isDarkTheme={this._isDarkTheme}
          environmentMessage={this._environmentMessage}
          hasTeamsContext={!!this.context.sdks.microsoftTeams}
          userDisplayName={this.context.pageContext.user.displayName}
          listName={this.properties.listName}
          folderName={this.properties.folderName}
          Order={this.properties.Order}
          context={this.context}
        />
      );
    };

    ReactDom.render(<Element />, this.domElement);
  }

  protected async onInit(): Promise<void> {
    this._environmentMessage = this._getEnvironmentMessage();
    await super.onInit();
    getSP(this.context);
  }

  private _getEnvironmentMessage(): Promise<string> {
    if (!!this.context.sdks.microsoftTeams) {
      // running in Teams, office.com or Outlook
      return this.context.sdks.microsoftTeams.teamsJs.app
        .getContext()
        .then((context) => {
          let environmentMessage: string = "";
          switch (context.app.host.name) {
            case "Office": // running in Office
              environmentMessage = this.context.isServedFromLocalhost
                ? strings.AppLocalEnvironmentOffice
                : strings.AppOfficeEnvironment;
              break;
            case "Outlook": // running in Outlook
              environmentMessage = this.context.isServedFromLocalhost
                ? strings.AppLocalEnvironmentOutlook
                : strings.AppOutlookEnvironment;
              break;
            case "Teams": // running in Teams
              environmentMessage = this.context.isServedFromLocalhost
                ? strings.AppLocalEnvironmentTeams
                : strings.AppTeamsTabEnvironment;
              break;
            default:
              throw new Error("Unknown host");
          }

          return environmentMessage;
        });
    }

    return Promise.resolve(
      this.context.isServedFromLocalhost
        ? strings.AppLocalEnvironmentSharePoint
        : strings.AppSharePointEnvironment
    );
  }

  protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
    if (!currentTheme) {
      return;
    }

    this._isDarkTheme = !!currentTheme.isInverted;
    const { semanticColors } = currentTheme;

    if (semanticColors) {
      this.domElement.style.setProperty(
        "--bodyText",
        semanticColors.bodyText || null
      );
      this.domElement.style.setProperty("--link", semanticColors.link || null);
      this.domElement.style.setProperty(
        "--linkHovered",
        semanticColors.linkHovered || null
      );
    }
  }

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

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

  private loadLists(): Promise<IPropertyPaneDropdownOption[]> {
    const restAPiUrl: string = `${this.context.pageContext.web.absoluteUrl}/_api/web/lists`;
    const listTitles: IPropertyPaneDropdownOption[] = [];

    return new Promise<IPropertyPaneDropdownOption[]>(
      (
        resolve: (options: IPropertyPaneDropdownOption[]) => void,
        reject: (error: ErrorCallback) => void
      ) => {
        this.context.spHttpClient
          .get(restAPiUrl, SPHttpClient.configurations.v1)
          .then((response: SPHttpClientResponse) => {
            response.json().then((results) => {
              results.value
                .filter(
                  (result: { Hidden: boolean }) => result.Hidden === false
                )
                .map((result: { Title: string }) => {
                  listTitles.push({
                    key: result.Title,
                    text: result.Title,
                  });
                });
              // JSON.parse(JSON.stringify(listTitles));
              resolve(listTitles);
            });
          })
          .catch((err) => {
            reject(err);
          });
      }
    );
  }
  private loadFolders(): Promise<IPropertyPaneDropdownOption[]> {
    return new Promise<IPropertyPaneDropdownOption[]>((resolve, reject) => {
      try {
        this._sp = getSP();

        console.log(this.properties.listName);

        const folderList: IPropertyPaneDropdownOption[] = [];
        console.log("folderlist", folderList);
        const folder = this._sp.web.lists
          .getByTitle(this.properties.listName)
          .rootFolder.folders.filter(
            "Name ne 'Forms' and Name ne 'Attachments'"
          )();

        folder.then((item) => {
          console.log(item);
          if (item.length > 0) {
            item.map(({ Name }) => {
              folderList.push({
                key: Name,
                text: Name,
              });
            });
            console.log(JSON.parse(JSON.stringify(folderList)));
            resolve(folderList);
          } else {
            folderList.push({
              key: "None",
              text: "Aucun dossier disponible",
            });
            resolve(folderList);
          }
        });
      } catch (err) {
        console.log(err);
        reject(err);
      }
    });
  }

  protected onPropertyPaneConfigurationStart(): void {
    this.listsDropdownDisabled = !this.lists;
    this.foldersDropdownDisabled = !this.properties.listName || !this.folders;

    if (this.lists) {
      console.log("pas d'init");
      return;
    }

    this.context.statusRenderer.displayLoadingIndicator(
      this.domElement,
      "options"
    );

    this.loadLists()
      .then(
        (
          listOptions: IPropertyPaneDropdownOption[]
        ): Promise<IPropertyPaneDropdownOption[]> => {
          this.lists = listOptions;
          this.listsDropdownDisabled = false;
          this.context.propertyPane.refresh();
          return this.loadFolders();
        }
      )
      .then((folderOptions: IPropertyPaneDropdownOption[]): void => {
        this.folders = folderOptions;
        this.foldersDropdownDisabled = !this.properties.listName;
        this.context.propertyPane.refresh();
        this.context.statusRenderer.clearLoadingIndicator(this.domElement);
        this.render();
      });
  }

  protected onPropertyPaneFieldChanged(
    propertyPath: string,
    oldValue: string,
    newValue: string
  ): void {
    if (propertyPath === "listName" && newValue) {
      console.log("changed");
      // push new list value
      super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
      // get previously selected item
      const previousItem: string = this.properties.folderName;
      // reset selected item
      this.properties.folderName = undefined;
      this.properties.metaData = undefined;
      // push new item value
      this.onPropertyPaneFieldChanged(
        "folderName",
        previousItem,
        this.properties.folderName
      );
      // disable item selector until new items are loaded
      this.foldersDropdownDisabled = true;
      // refresh the item selector control by repainting the property pane
      this.context.propertyPane.refresh();
      // communicate loading items
      this.context.statusRenderer.displayLoadingIndicator(
        this.domElement,
        "folder"
      );

      this.loadFolders().then(
        (folderOptions: IPropertyPaneDropdownOption[]): void => {
          // store items
          this.folders = folderOptions;
          console.log("itemoptions: ", this.folders);
          // enable item selector
          this.foldersDropdownDisabled = false;
          this.context.propertyPane.refresh();
          // clear status indicator
          this.context.statusRenderer.clearLoadingIndicator(this.domElement);
          // refresh the item selector control by repainting the property pane
          this.context.propertyPane.refresh();
          // re-render the web part as clearing the loading indicator removes the web part body
          this.render();
        }
      );
    } else {
      super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
    }
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription,
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneDropdown("listName", {
                  label: strings.DescriptionFieldLabel,
                  options: this.lists,
                  disabled: this.listsDropdownDisabled,
                }),
                PropertyPaneDropdown("folderName", {
                  label: strings.FolderFieldLabel,
                  selectedKey: null,
                  options: this.folders,
                  disabled: this.foldersDropdownDisabled,
                }),
                PropertyPaneDropdown("metaData", {
                  label: strings.MetaDataFieldLabel,
                  selectedKey: null,
                  options: this.metaData,
                  disabled: this.foldersDropdownDisabled,
                }),
                PropertyPaneChoiceGroup("Order", {
                  label: strings.OrderFieldLabel,
                  options: [
                    { key: "true", text: "ASC" },
                    { key: "false", text: "DESC" },
                  ],
                }),
              ],
            },
          ],
        },
      ],
    };
  }
}

I have the same issue as him but I saw no resolution for him: TypeError: Cannot read properties of undefined (reading 'pageContext') when using sp pnp v3

Can you help me please ?

I tried to almost every configuration and i used older version to test it out but nothing helped.


Solution

  • Probably the "web" package is excluded by optimizer, try adding it explicilty to your import list:

    ....
    import { SPFI } from "@pnp/sp";
    ...
    import "@pnp/sp/web"; // <<< this