Question

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.

No correct solution

OTHER TIPS

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 have a working solution now.

Github repo

Licensed under: CC-BY-SA with attribution
Not affiliated with sharepoint.stackexchange
scroll top