0

I'm trying to create a modern webpart that connects to MSGraphClient using the UI Fabric, to create quick access to files.

I'm getting the follow error calling an async function with _allItems

(property) AatResources._allItems: IDocument[] Type 'Promise' is missing the following properties from type 'IDocument[]': length, pop, push, concat, and 16 more.ts(2740)

Any help would be much appreciated

so my code

    export interface IDocument {
      key: string;
      name: string;
      value: string;
      iconName: string;
      webUrl:string;
      fileType: string;
      modifiedBy: string;
      dateModified: string;
      dateModifiedValue: number;
      fileSize: string;
      fileSizeRaw: number;
      contentType:string;
      parentId:string;
      parentDriveId:string;
    } 



     export default class AatResources extends React.Component<IAatResourcesProps, IAatResourcesState> {
      private _allItems: IDocument[];
          constructor(props: IAatResourcesProps) {
              super(props);
              this._allItems = _getDocuments('shared', default_dir_id , default_drive_id, '', '');
          }
      // render, etc...
      }

The async function

MSGraph class is from https://www.techmikael.com/2018/09/example-of-wrapper-to-ease-usage-of.html

  async function _getDocuments(drive_type: any, dir_id: any, drive_id: any, action: any, item_id: any) : Promise<IDocument[]>  {

  const items: IDocument[] = []; 
  let graphUrl :string; 

  await MSGraph.Init(this.context);

  if (drive_type == 'me' && !dir_id) {
    graphUrl = "/me/drive/root/children";
  }

  let response = await MSGraph.Get(graphUrl);

  if (response) {
    var contentType;
    response.value.map((item: any) => {
      if(item.folder){
        contentType = 'folder';
      }else{
        contentType = 'file';
      }
      items.push({
        key:item.id,
        name:item.name,
        value:item.name,
        iconName:'',
        fileType: '',
        webUrl:item.webUrl,
        fileSize: item.size,
        fileSizeRaw: item.size,
        dateModified: item.lastModifiedDateTime,
        modifiedBy: item.lastModifiedBy.user.displayName,
        dateModifiedValue: item.lastModifiedDateTime,
        contentType:contentType,
        parentId:item.parentReference.id,
        parentDriveId:item.parentReference.driveId,
      });
    });

    this.setState({
      items: items
    });

    return items
  }

}

alternatively using a sync function with a callback, I don't get the error but I'm having trouble returning the items array

function _getDocuments(drive_type: any, dir_id: any, drive_id: any, action: any, item_id: any) {

  let graphUrl :string;

  const items: IDocument[] = [];

  if (drive_type == 'me' && !dir_id) {
    graphUrl = "/me/drive/root/children";
  }

    this.props.context.msGraphClientFactory
    .getClient()
    .then((client: MSGraphClient): void => {
      client
        .api(graphUrl)
        .version("v1.0")
        .get((error, response: any, rawResponse?: any) => {

          if (error) {
            console.error(error);
            return;
          }
          if (response) {
            //console.log(response);
            //var driveItems: Array<IDocument> = new Array<IDocument>();
            var contentType;
            response.value.map((item: any) => {
              if(item.folder){
                contentType = 'folder';
              }else{
                contentType = 'file';
              }
              items.push({
                key:item.id,
                name:item.name,
                value:item.name,
                iconName:'',
                fileType: '',
                webUrl:item.webUrl,
                fileSize: item.size,
                fileSizeRaw: item.size,
                dateModified: item.lastModifiedDateTime,
                modifiedBy: item.lastModifiedBy.user.displayName,
                dateModifiedValue: item.lastModifiedDateTime,
                contentType:contentType,
                parentId:item.parentReference.id,
                parentDriveId:item.parentReference.driveId,
              });
            });
            //add callback ?
          }
        });
    });

    this.setState({
      items: items
    });
    return items;
}

What I don't get, if I call the example function from Microsoft (https://developer.microsoft.com/en-us/fabric#/controls/web/detailslist) it doesn't error or say there's a missing promise, it works fine!

function _getDocuments(){

  const items: IDocument[] = [];

  for (let i = 0; i < 500; i++) {
    const randomDate = _randomDate(new Date(2012, 0, 1), new Date());
    const randomFileSize = '0';
    const randomFileType = _randomFileIcon();
    let fileName = 'test1';
    fileName = fileName.charAt(0).toUpperCase() + fileName.slice(1).concat(`.${randomFileType.docType}`);
    let userName = 'un';
    userName = userName
      .split(' ')
      .map((name: string) => name.charAt(0).toUpperCase() + name.slice(1))
      .join(' ');
    items.push({
      key: i.toString(),
      name: fileName,
      value: fileName,
      iconName: randomFileType.url,
      fileType: randomFileType.docType,
      webUrl:'',
      modifiedBy: userName,
      dateModified: randomDate.dateFormatted,
      dateModifiedValue: randomDate.value,
      fileSize: '0',
      fileSizeRaw: 0,
      contentType:'',
      parentId:'',
      parentDriveId:'',
    });
  }
  return items;
}

I've tried removing the parameters and testing it by declaring within the function, but still errors.

0

In your async version, you are returning the items array (of type IDocument[]), but an async function needs to return a promise. The promise can contain a reference to the populated array.

React will handle it for you, if you specify async, but then also specify the return type to be a typed Promise. It will take your returned array and wrap it in a promise for you. In your case, you want to set return type of _getDocuments to Promise<IDocument[]>, so your declaration would look something like this:

async function _getDocuments(drive_type: any, dir_id: any, drive_id: any, action: any, item_id: any) : Promise<IDocument[]> {

In addition, in order for React to be able to interpret your return state as the resolving of a Promise, you first have to return the Promise, then you can return the results. Give this version a try:

function _getDocuments(drive_type: any, dir_id: any, drive_id: any, action: any, item_id: any) : Promise<IDocument[]> {

  let graphUrl :string;

  const items: IDocument[] = [];

  if (drive_type == 'me' && !dir_id) {
    graphUrl = "/me/drive/root/children";
  }

    return this.props.context.msGraphClientFactory
    .getClient()
    .then((client: MSGraphClient): void => {
      client
        .api(graphUrl)
        .version("v1.0")
        .get((error, response: any, rawResponse?: any) => {

          if (error) {
            console.error(error);
          }

          if (response) {
            //console.log(response);
            //var driveItems: Array<IDocument> = new Array<IDocument>();
            var contentType;
            response.value.map((item: any) => {
              if(item.folder){
                contentType = 'folder';
              }else{
                contentType = 'file';
              }
              items.push({
                key:item.id,
                name:item.name,
                value:item.name,
                iconName:'',
                fileType: '',
                webUrl:item.webUrl,
                fileSize: item.size,
                fileSizeRaw: item.size,
                dateModified: item.lastModifiedDateTime,
                modifiedBy: item.lastModifiedBy.user.displayName,
                dateModifiedValue: item.lastModifiedDateTime,
                contentType:contentType,
                parentId:item.parentReference.id,
                parentDriveId:item.parentReference.driveId,
              });
            });

            this.setState({
               items: items
            });

             return items;
          }
        });
    });
}

Note the first return happens where you initiate the asynchronous call to the Graph API, this allows your function to return quickly and continue performing the rest of the operation as normal. Then, once you have assembled your result set, you can return your items, which fulfills the promise.

| improve this answer | |
  • Thanks @willman, I have tried this and I did have this on but it made no difference – AAT Jan 21 at 15:39
  • Can you updated your code in question with what it looks like after you implement my suggestion. At the very least you should be getting a different error message than you were before. – willman Jan 21 at 17:25
  • OK I've updated the code in the question, I've checked gain and still get the same error – AAT Jan 21 at 22:55
  • What I don't get, if I call the example function from Microsoft (developer.microsoft.com/en-us/fabric#/controls/web/detailslist) it doesn't error or say there's a missing promise, it works fine! – AAT Jan 22 at 10:44
  • No, that error is not about the call you are making, it's about the way you are returning data from your getDocuments function. Async functions need to return a Promise that method will complete later, because it is asynchronous, it would return before the results are complete. Try removing the return items; from the end of your function, and instead put it where you have //add callback comment. – willman Jan 22 at 14:01
0

I resolved this by moving it in to a public modifier

public async _getDocuments(....

I also had to bind 'this' as this.props.context.msGraphClientFactory was undefined using

import autobind from 'react-autobind';

then adding in the class

constructor(props: IProps) {
    super(props);
    autobind(this)
...
| improve this answer | |

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.