0

I came up with the code below, but receiving errors and is likely not the way to do it.

https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-aadhttpclient-enterpriseapi < There is an example here for a vanilla js spfx app, but not a react one :(

Is anybody able to point me in the right direction on how I can achieve consuming an azure function (secured by AD) inside a React spfx app? Thank you!

import * as React from 'react';
import styles from './SecureFinalAd.module.scss';
import { ISecureFinalAdProps } from './ISecureFinalAdProps';
import { AadHttpClient, HttpClientResponse } from '@microsoft/sp-http';

export default class SecureFinalAd extends React.Component<ISecureFinalAdProps, {}> {

  public ordersClient: AadHttpClient;

  protected onInit(): Promise<void> {
    return new Promise<void>((resolve: () => void, reject: (error: any) => void): void => {
      this.context.aadHttpClientFactory
        .getClient('c3e425a8-e8b4-408d-8370-43b9abbb23c4')
        .then((client: AadHttpClient): void => {
          this.ordersClient = client;
          resolve();
        }, err => reject(err));
    });
  }

  public callThis(){
    this.ordersClient
      .get('https://spfx-testing.azurewebsites.net/api/spfx-testing', AadHttpClient.configurations.v1)
      .then((res: HttpClientResponse): Promise<any> => {
        return res.json();
      }), (err: any): void => {
        console.log(err);
      };
  }

  componentDidMount() {
    this.callThis();
  }

  
}

enter image description here

2
  • 1
    What errors are you getting? If you edit your question and add information about the specific errors you are seeing it will make it much easier for people to help figure out how to solve them. Nov 30, 2020 at 17:38
  • I understand and have added the errors. To be honest I am more looking for a resource for a design pattern to follow rather than fix the errors in the code, unless someone is able to spot a quick fix with the errors then that would be amazing!
    – BennKingy
    Nov 30, 2020 at 19:04

2 Answers 2

2

Based on the error you posted, this seems like a timing issue, possibly compounded by trying to implement a function/event from one framework's lifecycle in a different framework.

First, the timing issue. Clearly the error you are getting that cannot read property get of undefined refers to your line of code where you try calling this.ordersClient.get(). That's most likely happening because

componentDidMount() {
    this.callThis(); // <-- 1. first this is called, then...
}

public callThis(){
    this.ordersClient.get() // <-- 2. this is called...
        .then((res: HttpClientResponse): Promise<any> => {
            return res.json();
        }), (err: any): void => {
            console.log(err);
        };
}

protected onInit(): Promise<void> {
    return new Promise<void>((resolve: () => void, reject: (error: any) => void): void => {
    this.context.aadHttpClientFactory
        .getClient('c3e425a8-e8b4-408d-8370-43b9abbb23c4')
        .then((client: AadHttpClient): void => {
        this.ordersClient = client; // <-- 3. BEFORE this happens
        resolve();
        }, err => reject(err));
    });
}

I have no idea whether the initial firing of the onInit() function happens before or after the initial firing of componentDidMount(), the main point to consider is that this.context.aadHttpClientFactory.getClient() is asynchronous, and no matter when the initial call goes out, clearly the code that gets to this.ordersClient.get() is trying to call get on an AadHttpClient that isn't there yet.

That does bring us to the second idea though -- when I say

I have no idea whether the initial firing of the onInit() function happens before or after the initial firing of componentDidMount()

the other thing to consider there is that you pulled your example of using onInit() from that article, where the onInit() function is implemented inside a class that extends BaseClientSideWebPart. From the article you linked to:

export default class OrdersWebPart extends BaseClientSideWebPart<IOrdersWebPartProps> {
    private ordersClient: AadHttpClient;

    protected onInit(): Promise<void> {
        // shortened for brevity
    }
}

However, in your actual code, you are trying to implement onInit() inside a class that extends React.Component. From your code:

export default class SecureFinalAd extends React.Component<ISecureFinalAdProps, {}> {

    public ordersClient: AadHttpClient;

    protected onInit(): Promise<void> {
        // shortened for brevity
    }

    public callThis() {
        // shortened for brevity
    }

    componentDidMount() {
        // shortened for brevity
    }
}

What I would assume is that onInit() is a function that is called as part of the SharePoint Framework lifecycle, and so therefore when you try to implement it inside a class that extends React.Component, it's actually just like defining a regular function, and since onInit() is not part of the React lifeceycle, your onInit function is never even getting called, which compounds the timing issues mentioned above, because if your onInit is never called, then the async call to aadHttpClientFactory.getClient() never happens, so of course it never even has a chance to complete and retrieve the client object.

What you could try, since your component is extending React.Component and you are tied in to the React lifecycle events, is abandoning onInit completely and just start the whole process of getting your AadHttpClient from componentDidMount. Something along the lines of:

export default class SecureFinalAd extends React.Component<ISecureFinalAdProps, {}> {

    public aadClient: AadHttpClient;
    public myFunctionResponse: any;

    componentDidMount() {
        // get the client object
        this.context.aadHttpClientFactory
            .getClient('c3e425a8-e8b4-408d-8370-43b9abbb23c4')
            .then((client: AadHttpClient): void => {

                // now that you have the client,
                // save it so you can access it
                // later if you need to
                this.aadClient = client;

                // BUT - you still want to call your
                // Azure function as soon as you have
                // access to the client, so go ahead
                // and do that
                this.aadClient
                    .get('https://spfx-testing.azurewebsites.net/api/spfx-testing', AadHttpClient.configurations.v1)
                    .then((res: HttpClientResponse): void => {

                        // HttpClientResponse.json() is an async
                        // function in and of itself, so you need to
                        // treat is as such in order to get to the _actual_
                        // response content from your Azure function

                        res.json().then((azureFunctionJson: any): void => {

                            // do something with the actual response content
                            console.log(azureFunctionJson);
                            this.myFunctionResponse = azureFunctionJson;
                        });
                    }), (err: any): void => {
                        console.log(err);
                    };

        }, (err: any): void => {
            console.log(err);
        });
    }
}

(Note: because the original example you linked to used an instance property, which might be appropriate for something that extends the SPFx class BaseClientSideWebPart, and you used the same pattern in your class, I also used that for the additional property storing the response, however the question of when to use instance properties vs. state in React is a whole other can of worms.)

1

This is the design pattern I used to get to this working :)

1:

  componentDidMount() {
    this.callAzureFunctionAd("Benn", "Green");
  }

  public callAzureFunctionAd(name, colour) {

    const payLoad = {
      name: name,
      colour: colour
    }

    this.props.context.aadTokenProviderFactory
      .getTokenProvider()
      .then((tokenProvider: AadTokenProvider) => {
        return tokenProvider.getToken('https://spfx-testing.azurewebsites.net'); 
      })
      .then((accessToken: string): void => {
        $.post({
          url: 'https://spfx-testing.azurewebsites.net/api/HttpTrigger1',
          headers: {
            authorization: `Bearer ${accessToken}`,
            'Content-Type': 'application/json'
          },
          data: JSON.stringify(payLoad)
        })
        .done(response => {
          console.log(response);
        });
      })
  }

2:

import { WebPartContext } from "@microsoft/sp-webpart-base";

export interface ISecureFinalAdWebPartProps {
  description: string;
  context: WebPartContext;
}

export default class SecureFinalAdWebPart extends BaseClientSideWebPart<ISecureFinalAdWebPartProps> {

  constructor() {
    super();
  }

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

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

Your Answer

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

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