pnp / pnpjs

Fluent JavaScript API for SharePoint and Microsoft Graph REST APIs
https://pnp.github.io/pnpjs/
Other
764 stars 304 forks source link

TypeError: Cannot read properties of undefined (reading 'web') #3075

Closed snitgaard closed 4 months ago

snitgaard commented 4 months ago

Major Version

2.x (No longer supported)

Minor Version Number

2.15.0

Target environment

SharePoint Framework

Additional environment details

I'm using Typescript React, pnp/sp, NodeJS and Visual Studio Code for a SPFX application.

I know Version 2 is no longer supported but I'd appreciate some help since this is completely outside my control

Expected or Desired Behavior

Usual workbench launch and entering preview mode for debugging.

Observed Behavior

As soon as I open "Preview" mode on my SPFX workbench, the site crashes and I'm met with:

toabsoluteurl.js:27 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'web')
    at toabsoluteurl.js:27:46
    at step (tslib.es6.js:102:1)
    at Object.next (tslib.es6.js:83:45)
    at tslib.es6.js:76:1
    at new Promise (<anonymous>)
    at __awaiter (tslib.es6.js:72:1)
    at toAbsoluteUrl (toabsoluteurl.js:12:21)
    at operations.js:24:59
    at step (tslib.es6.js:102:1)
    at Object.next (tslib.es6.js:83:45)

This is the path to the file: webpack:///node_modules/@pnp/sp/utils/toabsoluteurl.js

And the line of code thats giving me errors:

 if (context) {
                return [2 /*return*/, combine(context.pageContext.web.absoluteUrl, candidateUrl)];
            }

This worked fine a week ago. It's an error that has started happening within the last week or so, and I have made no changes to the code in question. I also tried on 4 different applications, and they all face the same issue of crashing as soon as you try to enter Preview mode.

Steps to Reproduce

I run gulp serve in my application, wait for the app to be ready for debugging, select the app from list of available apps and enter Preview mode. I use pnp/sp version 2.15.0 with React 16.9.0.

snitgaard commented 4 months ago

A workaround I've found is to instead of using workbench, I append the following tag after the sharepoint site in the url: ?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js

So the url would be: https://tenant.sharepoint.com/sites/site?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js

This is just the main page using the localhosted manifest meaning it gets the changes the same way the workbench would get it.

This also just further confirms that something is wrong internally with the way workbench and pnp communicates.

juliemturner commented 4 months ago

What that error says to me is that the context you passed in (i.e. your point about the workbench) isn't passing a valid context ergo this line context.pageContext.web.absoluteUrl, web is undefined. If you want to share more of your project, i.e. how you establish the sp context I can try to help you further, it may be that the way you're initializing things is timed incorrectly... otherwise I'm not sure I can help more. You might try inspecting this.context that you're passing into the sp object when you initialize pnpjs during runtime to see if there's something obvious there.

snitgaard commented 4 months ago

@juliemturner The thing is that it suddenly started causing issues out of nowhere. It's worked fine for several years before this, and even worked fine last week. context.pageContext.web.absoluteUrl this line specifically is not something I have written, it is part of the pnp node module file, meaning I can't change it. Also my last reply also talks about how the issue is probably within the workbench of Sharepoint, as the issue works fine in my workaround and when the solution is deployed.

Though, I am curious about this whole thing so if you can help that would be great. This is how the initial sp context is established:

public render(): void {
    const element: React.ReactElement<CatalogProps> = React.createElement(
      Catalog,
      {
        description: this.properties.description,
        webPartContext: this.context
      }
    );

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

This is within the base webpart file. I have tried to console log the this.context and it shows the page context being filled etc within this initialization. Since I'm creating the element Catalog, I'm passing the context to that class in the form of a prop.

In the catalog class, I then further use the connection from the base component to create a class containing all the API calls: this.pnpHelper = new PnPHelper(this.props.webPartContext);

This "PnPHelper" class is basically all my CRUD functions. In here I do the following:

    constructor(wpContext: any) {
        this.webPartContext = wpContext;
        sp.setup({
            sp: {
                headers: {
                    "Accept": "application/json; odata=verbose",
                    "ContentType": "application/json; odata=verbose"
                }
            },
            // set ie 11 mode
            ie11: true,
            spfxContext: wpContext,
            defaultCachingStore: "local",
        });
        this.webPartContext = wpContext;
}

This is to further use the context established.

Hopefully this helps give some sort of overview

juliemturner commented 4 months ago

context.pageContext.web.absoluteUrl line from PnPjs references SPFx this.context, so what I'm implying is the library has no control over it either, it's something you're passing in when you establish context for the sp object. What I'm suggesting is somehow when you pass spfxContex: wpContext to the sp.setup command it's likely that wpContext is undefined/null. I've seen that before when there's a timing issue or a page refresh, although never specifically on the workbench.

Normally in v2 since the sp object is global, I personally initialize the sp object in a in the root .ts file for the SPFx solution and did it in the onInit method, not in a separate class. So, I would have done the following:

protected async onInit(): Promise<void> {
    try {
      // Initialize PnPjs (simple way)
      let ie11Mode: boolean = (!!window.MSInputMethodContext && !!document["documentMode"]);
      sp.setup({ ie11: ie11Mode, spfxContext: this.context });

      // Initialize PnPjs (modify headers, etc)
      sp.setup({
            sp: {
                headers: {
                    "Accept": "application/json; odata=verbose",
                    "ContentType": "application/json; odata=verbose"
                }
            },
            // set ie 11 mode
            ie11: true,
            spfxContext: this.context,
            defaultCachingStore: "local",
        });
    } catch (err) {
      console.error("onInit", err);
    }
  }

You can still have your PnPHelper class, I encourage services like that is it abstracts away the logic for the data layer for the UI but you won't need to pass the context to it, you'll just use the sp object as it's already initialized, you just need to import it into that class file.

If you follow this guidance, then to your other question, there should be no reason whatsoever to send the context to your root react component.

snitgaard commented 4 months ago

I tried your setup and the workbench acted the exact same way as before, with the site crashing if I try to enter "Preview" mode.

I think the solution of just using the base site with a navigation URL to the manifest is the way to go, as it doesn't isolate the experience and you don't run into some common design issues either. I still believe the issue is in the communication between Workbench and the version of pnp I'm using, as I mentioned, this started happening out of nowhere and to all my applications running the same configuration setup.

Though I still appreciate you taking your time to try and help me find a solution, I dont believe this question was completely answered as your answer did not work either.

skfoday commented 4 months ago

I am having the a similar problem, my pnp version is "@pnp/sp": "^4.1.0",. I can get data from sharepoint but I can't add data in to sharepoint. I get the same error "TypeError: Cannot read properties of undefined (reading 'web')".

juliemturner commented 4 months ago

I have just tested to verify but cannot replicate the issue. My solution will both get and update data using PnPjs on the hosted (https://mytenant.sharepoint.com/sites/mysite/_layouts/15/workbench.aspx) workbench. If you're getting an error then it is related to your code or tenant, and sadly without a complete reproduction of your issue I cannot help more than that.

ZabbyCapurin commented 4 months ago

I am having the a similar problem, my pnp version is "@pnp/sp": "^4.1.0",. I can get data from sharepoint but I can't add data in to sharepoint. I get the same error "TypeError: Cannot read properties of undefined (reading 'web')".

Running into the same issue with all of our project solutions as well, regardless of the @pnp/sp version or SPFx version (we're currently holding off on upgrading to 1.19.0 due to I guess conflicts with @pnp/spfx-controls-react and @pnp/spfx-property-controls.

All of this started on 6/19 for us randomly with no code changes on our end.

juliemturner commented 4 months ago

@ZabbyCapurin - If you're using @pnp/spfx-controls-react and/or @pnp/spfx-property-controls and you can't update data then yes, your issue is with that library not PnPjs. I just double checked and @pnp/spfx-property-controls project is significantly out of date as it references v1 of PnPjs, similarly @pnp/spfx-controls-react is also referencing a now unsupported version of PnPjs in v2. I personally am not a user of these libraries, I prefer the one I maintain with @StfBauer called hTWOo but if you want to use them you will need to use the versions bundled with those libraries as v1/v2 of PnPjs is not compatible with v3+.

skfoday commented 4 months ago

@juliemturner we are not using neither of the control "@pnp/spfx-controls-react and/or @pnp/spfx-property-controls" .

Here is the package.json code { "name": "nudge-submission", "version": "0.0.1", "private": true, "engines": { "node": ">=18.17.1 <19.0.0" }, "main": "lib/index.js", "scripts": { "build": "gulp bundle", "clean": "gulp clean", "test": "gulp test" }, "dependencies": { "@fluentui/react": "^8.106.4", "@microsoft/sp-component-base": "1.19.0", "@microsoft/sp-core-library": "1.19.0", "@microsoft/sp-lodash-subset": "1.19.0", "@microsoft/sp-office-ui-fabric-core": "1.19.0", "@microsoft/sp-property-pane": "1.19.0", "@microsoft/sp-webpart-base": "1.19.0", "@pnp/graph": "^4.1.0", "@pnp/logging": "^4.2.0", "@pnp/sp": "^4.1.0", "react": "17.0.1", "react-dom": "17.0.1", "tslib": "2.3.1" }, "devDependencies": { "@microsoft/eslint-config-spfx": "1.20.1", "@microsoft/eslint-plugin-spfx": "1.20.1", "@microsoft/rush-stack-compiler-4.7": "0.1.0", "@microsoft/sp-build-web": "1.20.1", "@microsoft/sp-module-interfaces": "1.20.1", "@rushstack/eslint-config": "2.5.1", "@types/react": "17.0.45", "@types/react-dom": "17.0.17", "@types/webpack-env": "~1.15.2", "ajv": "^6.12.5", "eslint": "8.7.0", "eslint-plugin-react-hooks": "4.3.0", "gulp": "4.0.2", "typescript": "4.7.4" } } and here is my tsx file

` import * as React from 'react'; import styles from './NudgeSubmission.module.scss';

import type { INudgeSubmissionProps } from './INudgeSubmissionProps'; import { spfi, SPFx as spSPFx } from '@pnp/sp' import "@pnp/sp/webs"; import "@pnp/sp/lists"; import "@pnp/sp/fields"; import "@pnp/sp/lists/web"; import "@pnp/sp/items"; import "@pnp/sp/site-users/web"; import "@pnp/sp/presets/all"; import { LogLevel, PnPLogging } from "@pnp/logging"; import { FontIcon } from '@fluentui/react/lib/Icon'; import { PrimaryButton } from '@fluentui/react/lib/Button';

 private async Submit(){
const sp = spfi(this.props.siteURL).using(spSPFx(this.context)).using(PnPLogging(LogLevel.Warning));

const getallitems = await sp.web.lists.getByTitle("Nudge Requests").items()   
console.log('Dataopen',getallitems) 

**const iar  = await sp.web.lists.getByTitle("Nudge Requests").items.add({
    Title: 'hello',
    SubjectId: this.state.choiceSelected
  })** //**THIS BIT  DOESNT WORK**
  .then(newResult => { console.log('Data added successfully!'); })
  .catch(reject => console.error('Error adding data to SharePoint list', reject));

let outroHeader = (<h1>Your request has been submitted</h1>)
let outroText = (<p> You will recieve your first nudge next Monday.</p>)

let Content = [outroHeader,outroText, <PrimaryButton text="Return to Start" onClick={this.buildIntro} /> ]

this.setState({Content : Content})
}

`

I am able to get items form sharepoints using this

const getallitems = await sp.web.lists.getByTitle("Nudge Requests").items() console.log('Dataopen',getallitems)

But I can't add items.

juliemturner commented 4 months ago

I'm sorry you comment implied you were using them, so I was responding to that.

As I said above, I cannot reproduce this issue. Couple of things I noted:

skfoday commented 4 months ago

@juliemturner If you remove SubjectId from your update payload does it work? : it doesn't work.

juliemturner commented 4 months ago

So then I have to assume that your sp object isn't getting correctly initialized. You said this is you tsx file, and given your package.json I assume this is SharePoint Framework project... and so then I suspect this.context is probably null or undefined when you're passing it into this line:

const sp = spfi(this.props.siteURL).using(spSPFx(this.context)).using(PnPLogging(LogLevel.Warning));

maybe you could add a console.log before that line to verify... but this.context is only available in the root ts file.

console.log("SPFx Context", this.context);

So unless you're doing something else I can't see from the snippet that's likely the issue.

skfoday commented 4 months ago

@juliemturner error I have attached an image from the console log console.log("SPFx Context", this.context); on the tsx file error_ts_file This is console log for the ts file

juliemturner commented 4 months ago

You say above "this is the console log for the ts file", that's fine but this.context doesn't exist in the tsx file. I was asking you to modify this method and put a console.log in before the const sp line.

private async Submit(){

console.log("SPFx Context", this.context);
const sp = spfi(this.props.siteURL).using(spSPFx(this.context)).using(PnPLogging(LogLevel.Warning));

const getallitems = await sp.web.lists.getByTitle("Nudge Requests").items()   
console.log('Dataopen',getallitems) 

**const iar  = await sp.web.lists.getByTitle("Nudge Requests").items.add({
    Title: 'hello',
    SubjectId: this.state.choiceSelected
  })** //**THIS BIT  DOESNT WORK**
  .then(newResult => { console.log('Data added successfully!'); })
  .catch(reject => console.error('Error adding data to SharePoint list', reject));

let outroHeader = (<h1>Your request has been submitted</h1>)
let outroText = (<p> You will recieve your first nudge next Monday.</p>)

let Content = [outroHeader,outroText, <PrimaryButton text="Return to Start" onClick={this.buildIntro} /> ]

this.setState({Content : Content})
}

At this point I'm pointing out to you why it's not working, the get's are working because of cookie auth but the posts will not without a proper token and that token needs to be passed into the spfi object. I would suggest you take a look at the getting started application that walks you though it or I have a blog post that goes deeper into how this setup needs to be done.

skfoday commented 4 months ago

my apologies I have now attached the console log errorNew

and I will go through the links that you sent.

juliemturner commented 4 months ago

No problem, but I think you now see what the issue is. The getting started app should show you a better way to set up your project.

juliemturner commented 4 months ago

I'm going to close this issue as answered. If you have further issues, please feel free to create a new issue and reference this one.

skfoday commented 4 months ago

@juliemturner Hi I am using your method using the pnpjsConfig.ts and I get this error message error3

juliemturner commented 4 months ago

I'm sorry that's not enough information to help you.

skfoday commented 4 months ago

Thank you for your help I have figured it out

github-actions[bot] commented 4 months ago

This issue is locked for inactivity or age. If you have a related issue please open a new issue and reference this one. Closed issues are not tracked.