0

I've made application SPFx with React. It's working when I enter the page where is the app. But when I come back or go on another page and then go back to the page with app, I'm getting error:

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

My code:

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

Component:

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;

I've tried to put logs in the code, change the way how to use context, etc. It;s like it looses context, but it shouldn't based on my code and my understanding.

2 Answers 2

1

Looks like the error that was solved. The relevant issue ticket is https://github.com/SharePoint/sp-dev-docs/issues/8795

Sign up to request clarification or add additional context in comments.

Comments

0

Just change your pnp class into and try again:

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!;
};

I have replicated your code and the way you have everything else should work

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.