-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Target SharePoint environment
SharePoint Online
What SharePoint development model, framework, SDK or API is this about?
💥 SharePoint Framework
Developer environment
Windows
What browser(s) / client(s) have you tested
- 💥 Internet Explorer
- 💥 Microsoft Edge
- 💥 Google Chrome
- 💥 FireFox
- 💥 Safari
- mobile (iOS/iPadOS)
- mobile (Android)
- not applicable
- other (enter in the "Additional environment details" area below)
Additional environment details
All Browsers, Most versions of SPFx (Currently using 1.21)
Describe the bug / error
Summary
During non-refresh (client-side) navigation in SPFx, the pageContext appears to be disposed while components and their associated code are still active in memory. This can lead to runtime errors when existing references to pageContext are accessed.
Background
I am a co-maintainer of https://github.com/pnp/pnpjs with @juliemturner and @patrick-rodgers. While investigating reported issues, we observed errors such as:
Cannot read properties of undefined (reading 'web')
This error is coming internally in PnPjs where we reference a Page Context Object. We use a closure and it seems the code is actively running and still holding a reference to a page object that is disposed. This has come up on many occasions an in many customer tenants.
Related discussion:
pnp/pnpjs#3334
Observed Behavior
- During client-side navigation:
- The SPFx
pageContextobject is marked as disposed (disposed = true) and becomes undefined. - At the same time, web parts and their JavaScript execution context are still present and executing.
- The SPFx
- Any existing references to
pageContext(e.g., held via closures or long-lived objects) can become invalid. - Accessing properties such as
pageContext.webresults in runtime exceptions.
Debug Findings
- The
pageContextfrom the internal SharePointsp-page assembly:- Is marked as disposed
- Has
_isDisposed = true
- Meanwhile:
- The associated components are not yet torn down
- Code paths that rely on previously captured references are still invoked
See below where I caught breakpoints in live running code in the internals of the page assembly,
I would love to know if this is an issue with how we're using the pageContext internally or if this is a larger bug in the SharePoint page lifecycle
Steps to reproduce
Copied from our repo
- Setup a HelloWorld web part as described below.
- Add this to the home page of a communication site
- Setup another page in the comms site, with a link in the navigation
- Load home page, allow web part to render (works this time)
- Use navigation to go to other page
- Use navigation to return to the home page
- Observe error
Create a HelloWorld web part that initializes a common SPFi object via a centralized helper.
Web part init:
protected onInit(): Promise<void> {
getSP(this.context);
return this._getEnvironmentMessage().then(message => {
this._environmentMessage = message;
});
}
Centralized SPFi init:
/* eslint-disable no-var */
import { WebPartContext } from "@microsoft/sp-webpart-base";
// import pnp and pnp logging system
import { ISPFXContext, spfi, SPFI, SPFx as spSPFx } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/batching";
var _sp: SPFI;
export const getSP = (context?: WebPartContext): SPFI => {
if (_sp === undefined || _sp === 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(spSPFx(context as ISPFXContext));
}
return _sp;
};
Rendered component:
(error occurs on line 14)
import * as React from 'react';
import type { IPnPjsWebPartProps } from './IPnPjsWebPartProps';
import { useEffect, useState } from 'react';
import { getSP } from '../../../helpers/pnpjs-config';
const PnPjsWebPart: React.FunctionComponent<IPnPjsWebPartProps> = (props: IPnPjsWebPartProps) => {
const [message, setMessage] = useState<string>('Initial value, stil loading...');
useEffect(()=>{
// eslint-disable-next-line @typescript-eslint/no-floating-promises
(async ()=>{
try {
const sp = getSP();
const web = await sp.web(); // errors here on partial page reload
console.log(web);
setMessage('Got the web without an error! Hurrah!')
} catch (e) {
console.error('Error getting web :\'(', e);
setMessage((e as Error).message);
}
})();
},[])
return (
<section>
<div>{message}</div>
</section>
);
}
export default PnPjsWebPart;
Expected behavior
pageContextshould remain valid as long as dependent components are still active
or- Components should be disposed before
pageContextis invalidated