0

I am new to SharePoint and I am having trouble making API calls.

child component below:

import * as React from 'react';
import styles from './HelloWorld.module.scss';
import { IHelloWorldProps } from './IHelloWorldProps';
import { escape } from '@microsoft/sp-lodash-subset';
import {SPHttpClient, SPHttpClientResponse} from '@microsoft/sp-http';
import * as jquery from 'jquery';


export interface IHelloWordState{  
  items:[  
    {  
      "title":'',
      "endDate":''
    }
  ];  
} 

export default class HelloWorld extends React.Component<IHelloWorldProps, 
IHelloWordState> {

  public constructor( props:IHelloWorldProps, state:IHelloWordState ) {
    super(props);
    this.state = {
      items: [
        {
          "title":'',
          "endDate":''
        }
      ]
   }; 
}

public componentDidMount() {

  let run = 0;

  do {
    try {
      this.context.SPHttpClient.get(`${this.context.pageContext.web.absoluteUrl}/_api/lists/GetByTitle('ToDo')/items`,  SPHttpClient.configurations.v1)  
      .then((response: SPHttpClientResponse) => {  
          response.json().then((responseJSON: any) => {  
             console.log(responseJSON);  
          });  
       });
    } catch (e) {
      console.log(e);
      run++;
    }
  } while (run < 3);
}

public render(): React.ReactElement<IHelloWorldProps> {

    return (
       <div>
       </div>
    );
  }
}

parent component below:

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
  BaseClientSideWebPart,
  IPropertyPaneConfiguration,
  PropertyPaneTextField
} from '@microsoft/sp-webpart-base';

import * as strings from 'HelloWorldWebPartStrings';
import HelloWorld from './components/HelloWorld';
import { IHelloWorldProps } from './components/IHelloWorldProps';

export interface IHelloWorldWebPartProps {
  description: string;
  siteUrl:string;
}


export default class HelloWorldWebPart extends 
BaseClientSideWebPart<IHelloWorldWebPartProps> {


  public render(): void {
        const element: React.ReactElement<IHelloWorldProps > = React.createElement(HelloWorld,
      {
        description: this.properties.description,
        siteUrl: this.context.pageContext.site.absoluteUrl
      }

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

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

I am receving

'TypeError: Cannot read property 'get' of undefined at HelloWorld.componentDidMount'

I am having trouble figuring out what I am missing.

  • I started to write a reply, but I really need more information, namely more code. From what you've shown, componentDidMount looks like it's at module scope (i.e. not part of a class), but the error says HelloWorld.componentDidMount making it looks like it is part of a class? Question is, how did it become part of a class? Perhaps the whole definition of HelloWorld would clear things up? – Joe McShea Feb 8 at 21:55
  • Yes, 'componentDidMount()' it is part of the class HelloWorld. This class is the child component of HelloWorldWebPart.tsx – MAS89 Feb 8 at 22:05
  • @JoeMcShea, I've added some more of the code, maybe this will help. It's pretty standard web-part code. – MAS89 Feb 8 at 22:36
  • Excellent, that's enough. It could have been a few different things, but I can see the problem now. – Joe McShea Feb 9 at 0:26
1

Ok, you have a couple of problems.

Your first and simplest problem is that this.context is defined in BaseClientSideWebPart. So you can use it in HelloWorldWebPart, because that extends BaseClientSideWebPart. But you can't use it in HelloWorld because that is a react component and has no relationship or handle on BaseClientSideWebPart. If you want to use it in HelloWorld, you'd need to pass it somehow, for instance pass in this.context as another constructor argument to HelloWorld (the type of that constructor argument should be WebPartContext, which you'll need to import in the HelloWorld module from @microsoft/sp-webpart-base).

The second problem you're going to run into is an old fashioned JavaScript problem and confusion about how (this) works in JavaScript. I'm going to provide some very simple examples to demonstrate. Say you have a good old JavaScript class:

var myClass = function() {

    this.count = 0;

    this.incrementCount = function() {
        ++this.count;
    };
}

var instance = new myClass();

So this is a plain old JavaScript 'class', and I've instantiated it once as instance. Now I can use instance like so:

setTimeout(instance.incrementCount,1000);

After which I would expect instance.count to equal 1. But this code will actually blow up, because when setTimeout calls instance.incrementCount() after 1 second, inside instance.incrementCount you'll find that (this).count is undefined.

The reason is that (this) is not always the instance on which we were instantiated in JavaScript. When we're called externally, like in an asynchronous event receiver as we are with setTimeout, (this) is whatever it is for setTimeout. And since setTimeout is a global function, it's (this) is window (at least in the browser). And the odds are, there isn't a window.count so it's undefined.

Sticking with the plain old JavaScript for a moment, the old fashioned way around this was to capture (this) in a local variable, like so:

var myClass = function() {

    var _this = this;  // capture this

    this.count = 0;

    this.incrementCount = function() {
        ++_this.count;
    };
}

var instance = new myClass();

So I declare a local variable in my constructor called _this and store (this) in it. Then when I want to reference it in a function, I always reference it as _this because I can't count on (this). And through the magic of closures, _this will always point to my instance.

There is a second and better way around this problem, starting with ES6, called a Fat Arrow function (or just arrow function). And since you're doing TypeScript, which is a superset of ES6, it's available to you. This is the class with incrementCount redone with Fat Arrow syntax:

var myClass = function() {

    this.count = 0;

    // redeclared with Fat Arrow syntax
    this.incrementCount = () => {
        ++this.count;
    };
}

var instance = new myClass();

You just strip the word function, keep the parens and any arguments, and just before the implementation of the function you insert a Fat Arrow. Now this might seem foreign and ugly to you so why do this? Because when a function is declared with this syntax, (this) is always the instance the function was instantiated on.

This is the same situation you're facing. componentDidMount isn't called by you, it's called by the react framework, asynchronously. So even if you passed in the context in the constructor, componentDidMount still won't be able to find it because in that call (this) is going to point to lord only knows what (but something inside the react framework).

I think there may be a couple of other little problems in your code, but I'm not really setup to do React Spfx web parts at the moment, so without being able to compile it, these are the glaring issues that jumped out at me that need to be dealt with.

Hope it helps some.

  • I appreciate you taking the time to write this. It was helpful information for me. Once I resolve these issues I will add a post with the working solution. – MAS89 Feb 9 at 1:39
  • You're quite welcome. – Joe McShea Feb 9 at 1:41
1

I have a working solution now.

Github repo

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.