开发者问题收集

SPFx React - 错误无法读取未定义的属性(读取‘web’)

2024-01-26
459

我使用 React 制作了应用程序 SPFx。当我进入应用程序所在的页面时,它可以正常工作。但是当我返回或转到另一个页面,然后返回到包含应用程序的页面时,我收到错误:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'web')

我的代码:

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
  type IPropertyPaneConfiguration,
  PropertyPaneTextField,
  PropertyPaneCheckbox,
  PropertyPaneDropdown,
  PropertyPaneToggle
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base';

import * as strings from 'HelloWorldWebPartStrings';
import HelloWorld from './components/HelloWorld';
import { IHelloWorldProps } from './components/IHelloWorldProps';
import { spfi, SPFx } from "@pnp/sp";
import { getSP } from '../../pnpjsConfig';



export interface IHelloWorldWebPartProps {
  description: string;
}

export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {

  private _isDarkTheme: boolean = false;
  private _environmentMessage: string = '';

  public render(): void {
    const element: React.ReactElement<IHelloWorldProps> = React.createElement(
      HelloWorld,
      {
        description: this.properties.description,
        isDarkTheme: this._isDarkTheme,
        environmentMessage: this._environmentMessage,
        hasTeamsContext: !!this.context.sdks.microsoftTeams,
        userDisplayName: this.context.pageContext.user.displayName,
        context: this.context,
      }
    );

    ReactDom.render(element, this.domElement);
  }

  protected async onInit(): Promise<void> {
    this._environmentMessage = await this._getEnvironmentMessage();
    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
            case 'TeamsModern':
              environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
              break;
            default:
              environmentMessage = strings.UnknownEnvironment;
          }

          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');
  }

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

pnpjs:

import { WebPartContext } from "@microsoft/sp-webpart-base";
import { SPFI, SPFx, spfi } from "@pnp/sp";
import { LogLevel, PnPLogging } from "@pnp/logging";

import "@pnp/sp/web"
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/folders";
import "@pnp/sp/files";
import "@pnp/sp/files/folder";
import "@pnp/sp/batching";
import "@pnp/sp/attachments";

let _sp: SPFI | null = null;

export const getSP = (context?: WebPartContext): SPFI => {
    if (_sp === null && context != null) {
        _sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning))
    }
    return _sp as SPFI;
}

组件:

import * as React from 'react';
import type { IHelloWorldProps } from './IHelloWorldProps';
import { SPFI } from '@pnp/sp';
import { IDocumentItem } from '../../../interfaces';
import { getSP } from '../../../pnpjsConfig';
import ClientSelect from './ClientSelectProps';
import { ListView, IViewField, SelectionMode } from "@pnp/spfx-controls-react";
import { IItem } from "@pnp/sp/items/types";
import { IAttachmentInfo } from "@pnp/sp/attachments";


const HelloWorld = (props: IHelloWorldProps) => {
  // let _sp: SPFI = getSP(props.context) as SPFI;
  let _sp: SPFI;

  const [processName, setProcessName] = React.useState<string>('');
  const [clients, setClients] = React.useState<Array<{ Title: string, Id: string }>>([]);
  const [processes, setProcesses] = React.useState<Array<{ Title: string, Id: string }>>([]);
  const [allDocuments, setAllDocuments] = React.useState<Array<IDocumentItem>>([]);
  const [selectedClient, setSelectedClient] = React.useState<number>();

  React.useEffect(() => {
    _sp = getSP() as SPFI;
  }, [])

  React.useEffect(() => {
    const urlParams = new URLSearchParams(window.location.search);
    const myParam = urlParams.get('process');
    if (myParam) setProcessName(myParam);

    const fetchClients = async () => {
      console.log("TEST---------------", _sp.web);
      const items = await _sp.web.lists.getByTitle("Klienci").items();
      setClients(items);

      console.log({ items });

      const processList = await _sp.web.lists.getByTitle("Procesy").items();
      setProcesses(processList);

      console.log({ processList })

      const docs = await _sp.web.lists.getByTitle("Documents").items();
      const files = await _sp.web.getFolderByServerRelativePath("Documents").files();

      const filesTransformed = await Promise.all(files.map(async e => {
        const item = await _sp.web.getFileByServerRelativePath(e.ServerRelativeUrl).getItem<{ Id: number, Title: string }>("Id", "Title");
        return {
          id: item.Id,
          name: e.Name,
          path: `${e.ServerRelativeUrl}`,
        }
      }));

      console.log({ docs });
      console.log({ files });

      const documents = docs.map((doc, index) => {
        return {
          id: doc.Id,
          name: filesTransformed.find(e => e.id === doc.Id)?.name || "",
          path: filesTransformed.find(e => e.id === doc.Id)?.path || "",
          clintsIds: doc.KlienciId,
          processesIds: doc.ProcesyId
        }
      });

      const processId = processList.find(p => p.Title === myParam)?.Id;
      console.log({ selectedClient });
      console.log({ processId })
      const filteredDocuments = documents.filter(docu => docu.processesIds.includes(processId) && docu.clintsIds.includes(selectedClient));

      console.log({ filteredDocuments });

      setAllDocuments(filteredDocuments);
    }
    if (!_sp) {
      _sp = getSP(props.context) as SPFI;
    }
    if (processName && _sp) {
      fetchClients();
    }
  }, [processName, selectedClient])


  const viewFields: IViewField[] = [
    {
      name: 'id',
      displayName: 'ID',
      sorting: true,
      maxWidth: 70,
    },
    {
      name: 'name',
      displayName: 'Nazwa',
      sorting: true,
      maxWidth: 70,
    },
    {
      name: 'path',
      displayName: 'Link do dokumentu',
      sorting: true,
      maxWidth: 200,
      render: (item: any) => {
        return <a href={item['path']} target="_blank" rel="noopener noreferrer">Otwórz dokument</a>;
      }
    }
  ];

  return (
    <>
      <h1>{processName}</h1>
      <ClientSelect
        clients={clients}
        onChange={(selectedClientId) => {
          console.log("Wybrano klienta o ID:", selectedClientId);
          setSelectedClient(parseInt(selectedClientId));
        }}
      />
      <ListView
        items={allDocuments}
        viewFields={viewFields}
        iconFieldName="ServerRelativeUrl"
        compact={true}
        selectionMode={SelectionMode.multiple}
        showFilter={true}
        defaultFilter=""
        filterPlaceHolder="Search..."
        stickyHeader={true}
      />
    </>
  )
}

export default HelloWorld;

我尝试将日志放入代码中,更改使用上下文的方式等。这就像丢失了上下文,但根据我的代码和我的理解,它不应该如此。

2个回答

看起来错误已经解决。相关问题单为 https://github.com/SharePoint/sp-dev-docs/issues/8795

Eiglimar Junior
2024-04-02

只需将您的 pnp 类更改为并重试:

import { WebPartContext } from "@microsoft/sp-webpart-base";
 
// import pnp and pnp logging system you can insert the log if you need
import { spfi, SPFI, SPFx } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/batching";
import "@pnp/sp/items/get-all";
import "@pnp/sp/attachments";
import "@pnp/sp/site-users/web"
 
let _sp: SPFI;
 
export const getSP = (context?: WebPartContext): SPFI => {
  // eslint-disable-next-line eqeqeq
  if (context != null) { // eslint-disable-line eqeqeq
    // eslint-disable-next-line @rushstack/no-new-null
    //You must add the @pnp/logging package to include the PnPLogging behavior it is no longer a peer dependency
    // The LogLevel set's at what level a message will be written to the console
    _sp = spfi().using(SPFx(context))
  }
 
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  return _sp!;
};

我已经复制了您的代码,其他一切都应该可以正常工作

Andrés
2024-05-21