Question

I'm new to SPFX / React Development, So I thought I'd start with a simple project!

I want to add a Resource Quick Access webpart on my Modern Sharepoint Page where users can quickly access SharePoint/OneDrive/Team files etc... I am using the MSGraphClient with UI Fabric

I'm getting this error

(property) BaseWebPart.properties: AlphaResources This property is the pointer to the custom property bag of the web part. @readonly No overload matches this call. The last overload gave the following error. Argument of type '{ description: any; context: WebPartContext; }' is not assignable to parameter of type 'Attributes'. Object literal may only specify known properties, and 'description' does not exist in type 'Attributes'.ts(2769) index.d.ts(238, 14): The last overload is declared here.

I really don't understand why, I haven't had this in previous test web parts

So I could really do with your help.

in the webpart.ts I have

import * as React from 'react';
import * as ReactDom from 'react-dom';

import { Version } from '@microsoft/sp-core-library';
import {
        IPropertyPaneConfiguration,
        PropertyPaneTextField,
        PropertyPaneLabel,
        PropertyPaneToggle,
        PropertyPaneDropdown,
        PropertyPaneCheckbox,
        PropertyPaneLink,
        PropertyPaneSlider
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';

import * as strings from 'AlphaResourcesWebPartStrings';
import AlphaResources  from './components/AlphaResources';
import IAlphaResourcesProps from './components/AlphaResources';


export default class AlphaResourcesWebPart extends BaseClientSideWebPart<IAlphaResourcesProps> {

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

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

in the AlphaResources.tsx (code based on example [https://developer.microsoft.com/en-us/fabric#/controls/web/detailslist][1])

import * as React from 'react';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { MSGraphClient } from '@microsoft/sp-http';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Fabric } from 'office-ui-fabric-react/lib/Fabric';
import { Announced } from 'office-ui-fabric-react/lib/Announced';
import { DetailsList, DetailsListLayoutMode, Selection, SelectionMode, IColumn } from 'office-ui-fabric-react/lib/DetailsList';
import { mergeStyleSets } from 'office-ui-fabric-react/lib/Styling';
import { Pivot, PivotItem } from 'office-ui-fabric-react/lib/Pivot';
import { Nav, INavLink } from 'office-ui-fabric-react/lib/Nav';
import { Link } from 'office-ui-fabric-react/lib/Link';

const classNames = mergeStyleSets({
    fileIconHeaderIcon: {
    padding: 0,
    fontSize: '16px'
},
fileIconCell: {
    textAlign: 'center',
    selectors: {
    '&:before': {
    content: '.',
    display: 'inline-block',
    verticalAlign: 'middle',
    height: '100%',
    width: '0px',
    visibility: 'hidden'
}
}
},
fileIconImg: {
  verticalAlign: 'middle',
  maxHeight: '16px',
  maxWidth: '16px'
},
  controlWrapper: {
    display: 'flex',
    flexWrap: 'wrap'
  },
  exampleToggle: {
    display: 'inline-block',
    marginBottom: '10px',
    marginRight: '30px'
  },
  selectionDetails: {
    marginBottom: '20px'
  }
});
const controlStyles = {
  root: {
    margin: '0 30px 20px 0',
    maxWidth: '300px'
  }
};

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

export interface IAlphaResourcesState {
  columns: IColumn[];
  items: IDocument[];
  selectionDetails: string;
  isModalSelection: boolean;
  isCompactMode: boolean;
  announcedMessage?: string;
}

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 AlphaResources extends React.Component<{}, IAlphaResourcesState> {
  private _selection: Selection;
  private _allItems: IDocument[];

  constructor(props: {}) {
    super(props);

    var default_dir_id = "dir-id";
    var default_drive_id = 'drive-id';

    this._allItems = _getDocuments();

    const columns: IColumn[] = [
      {
        key: 'column1',
        name: 'File Type',
        className: classNames.fileIconCell,
        iconClassName: classNames.fileIconHeaderIcon,
        ariaLabel: 'Column operations for File type, Press to sort on File type',
        iconName: 'Page',
        isIconOnly: true,
        fieldName: 'name',
        minWidth: 16,
        maxWidth: 16,
        onColumnClick: this._onColumnClick,
        onRender: (item: IDocument) => {
          return <img src={item.iconName} className={classNames.fileIconImg} alt={item.fileType + ' file icon'} />;
        }
      },
      {
        key: 'column2',
        name: 'Name',
        fieldName: 'name',
        minWidth: 210,
        maxWidth: 350,
        isRowHeader: true,
        isResizable: true,
        isSorted: true,
        isSortedDescending: false,
        sortAscendingAriaLabel: 'Sorted A to Z',
        sortDescendingAriaLabel: 'Sorted Z to A',
        onColumnClick: this._onColumnClick,
        data: 'string',
        isPadded: true
      },
      {
        key: 'column3',
        name: 'Date Modified',
        fieldName: 'dateModifiedValue',
        minWidth: 70,
        maxWidth: 90,
        isResizable: true,
        onColumnClick: this._onColumnClick,
        data: 'number',
        onRender: (item: IDocument) => {
          return <span>{item.dateModified}</span>;
        },
        isPadded: true
      },
      {
        key: 'column4',
        name: 'Modified By',
        fieldName: 'modifiedBy',
        minWidth: 70,
        maxWidth: 90,
        isResizable: true,
        isCollapsible: true,
        data: 'string',
        onColumnClick: this._onColumnClick,
        onRender: (item: IDocument) => {
          return <span>{item.modifiedBy}</span>;
        },
        isPadded: true
      },
      {
        key: 'column5',
        name: 'File Size',
        fieldName: 'fileSizeRaw',
        minWidth: 70,
        maxWidth: 90,
        isResizable: true,
        isCollapsible: true,
        data: 'number',
        onColumnClick: this._onColumnClick,
        onRender: (item: IDocument) => {
          return <span>{item.fileSize}</span>;
        }
      }
    ];

    this._selection = new Selection({
      onSelectionChanged: () => {
        this.setState({
          selectionDetails: this._getSelectionDetails()
        });
      }
    });

    this.state = {
      items: this._allItems,
      columns: columns,
      selectionDetails: this._getSelectionDetails(),
      isModalSelection: false,
      isCompactMode: false,
      announcedMessage: undefined,
    };
  }


  public render() : JSX.Element {
    const {columns, isCompactMode, items, selectionDetails, isModalSelection, announcedMessage } = this.state;

    return (
      <Fabric>
<Pivot onLinkClick={this.onLinkClick}>
         <PivotItem headerText="Alpha Resources">
         <div className={classNames.controlWrapper}>
          <TextField label="Filter by name:" onChange={this._onChangeText} styles={controlStyles} />
          <Announced message={`Number of items after filter applied: ${items.length}.`} />
        </div>
        <DetailsList
            items={items}
            compact={isCompactMode}
            columns={columns}
            selectionMode={SelectionMode.none}
            getKey={this._getKey}
            setKey="none"
            layoutMode={DetailsListLayoutMode.justified}
            isHeaderVisible={true}
            onItemInvoked={this._onItemInvoked}
          />
        </PivotItem>
         <PivotItem headerText="My OneDrive">
           <Nav
             onLinkClick={this.onNavClick}
             styles={{
               root: {
                 width: 208,
                 height: 350,
                 boxSizing: 'border-box',
                 border: '1px solid #eee',
                 overflowY: 'auto'
               }
             }}
             groups={[
               {
                 links: [
                   {
                     name: 'My Files',
                     url:'#',
                     key: 'key1',
                   },
                   {
                     name: 'Shared',
                     url: '#',
                     key: 'key2',
                   },
                   {
                     name: 'Recent',
                     url: '#',
                     key: 'key3',
                   }
                 ]
               }
             ]}
           />
        <div className={classNames.controlWrapper}>
          <TextField label="Filter by name:" onChange={this._onChangeText} styles={controlStyles} />
          <Announced message={`Number of items after filter applied: ${items.length}.`} />
        </div>
        <DetailsList
            items={items}
            compact={isCompactMode}
            columns={columns}
            selectionMode={SelectionMode.none}
            getKey={this._getKey}
            setKey="none"
            layoutMode={DetailsListLayoutMode.justified}
            isHeaderVisible={true}
            onItemInvoked={this._onItemInvoked}
          />
      </PivotItem>
      <PivotItem headerText="Team Resources">
      <div className={classNames.controlWrapper}>
          <TextField label="Filter by name:" onChange={this._onChangeText} styles={controlStyles} />
          <Announced message={`Number of items after filter applied: ${items.length}.`} />
        </div>
        <DetailsList
            items={items}
            compact={isCompactMode}
            columns={columns}
            selectionMode={SelectionMode.none}
            getKey={this._getKey}
            setKey="none"
            layoutMode={DetailsListLayoutMode.justified}
            isHeaderVisible={true}
            onItemInvoked={this._onItemInvoked}
          />
      </PivotItem>
      </Pivot>
      </Fabric>
    );
  }
  public onLinkClick(item: PivotItem): void {
    alert(item.props.headerText);
  }
  public onNavClick(ev: React.MouseEvent<HTMLElement>, item?: INavLink) {
    if (item) {
      alert(item.name);
    }
  }
  public componentDidUpdate(previousProps: any, previousState: IAlphaResourcesState) {
    if (previousState.isModalSelection !== this.state.isModalSelection && !this.state.isModalSelection) {
      this._selection.setAllSelected(false);
    }
  }

  private _getKey(item: any, index?: number): string {
    return item.key;
  }

  private _onChangeCompactMode = (ev: React.MouseEvent<HTMLElement>, checked: boolean): void => {
    this.setState({ isCompactMode: checked });
  };

  private _onChangeModalSelection = (ev: React.MouseEvent<HTMLElement>, checked: boolean): void => {
    this.setState({ isModalSelection: checked });
  };

  private _onChangeText = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text: string): void => {
    this.setState({
      items: text ? this._allItems.filter(i => i.name.toLowerCase().indexOf(text) > -1) : this._allItems
    });
  };

  private _onItemInvoked(item: any): void {
    alert(`Item invoked: ${item.name}`);
  }

  private _getSelectionDetails(): string {
    const selectionCount = this._selection.getSelectedCount();

    switch (selectionCount) {
      case 0:
        return 'No items selected';
      case 1:
        return '1 item selected: ' + (this._selection.getSelection()[0] as IDocument).name;
      default:
        return `${selectionCount} items selected`;
    }
  }

  private _onColumnClick = (ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
    const { columns, items } = this.state;
    const newColumns: IColumn[] = columns.slice();
    const currColumn: IColumn = newColumns.filter(currCol => column.key === currCol.key)[0];
    newColumns.forEach((newCol: IColumn) => {
      if (newCol === currColumn) {
        currColumn.isSortedDescending = !currColumn.isSortedDescending;
        currColumn.isSorted = true;
        this.setState({
          announcedMessage: `${currColumn.name} is sorted ${currColumn.isSortedDescending ? 'descending' : 'ascending'}`
        });
      } else {
        newCol.isSorted = false;
        newCol.isSortedDescending = true;
      }
    });
    const newItems = _copyAndSort(items, currColumn.fieldName!, currColumn.isSortedDescending);
    this.setState({
      columns: newColumns,
      items: newItems
    });
  };
}

function _copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): T[] {
  const key = columnKey as keyof T;
  return items.slice(0).sort((a: T, b: T) => ((isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1));
}

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";
  }
  if (drive_type == 'shared' && !dir_id) {
    graphUrl = "/me/drive/sharedWithMe";
  }
  if (drive_type == 'recent' && !dir_id) {
    graphUrl = "/me/drive/recent";
  }
  if (drive_type == 'me' && dir_id) {
    graphUrl = "/me/" + dir_id + "/drive/root/children";
  }
  if (drive_type == 'shared' && dir_id && drive_id) {
    graphUrl = "/drives/" + drive_id + "/items/" + dir_id + "/children";
  }
  if (drive_type == 'recent' && dir_id) {
    graphUrl = "/drives/" + dir_id;
  }
  if (drive_type == 'groups' || drive_type == 'teams' && dir_id) {
    graphUrl = "/groups/" + dir_id + "/drive/root/children";
  }
//trigger if download required
  if(drive_type == 'me' && action=='download' && item_id){
    graphUrl="/me/drive/items/"+item_id+"/content";
  }

  //console.log(graphUrl)

    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,
              });
            });
            return items
            // Update the component state accordingly to the result  
            //console.log(items);
          }
        });
    });
}

No correct solution

OTHER TIPS

In your AlphaRessourcesWebPart you trying to create en element of AlphaRessources and giving it properties. The Problem is, that your AlphaRessources Class is not accepting Properties other than {}.

Here's your code from AlphaResources.tsx:

export default class AlphaResources extends React.Component<{}, IAlphaResourcesState> {
      private _selection: Selection;
      private _allItems: IDocument[];

      constructor(props: {}) {
        super(props);
    ...}

...}

to give properties to your component it should be like the following:

export default class AlphaResources extends React.Component<IAlphaResourcesProps, IAlphaResourcesState> {
  private _selection: Selection;
  private _allItems: IDocument[];

  constructor(props: IAlphaResourcesProps) {
    super(props);
  ...}
...}
Licensed under: CC-BY-SA with attribution
Not affiliated with sharepoint.stackexchange
scroll top