0

It's my first time trying out office ui fabric react JS to develop a web part app in SharePoint online. For a test (CRUD application) project, I have developed an online book library application. I have been struggling to save / get a date value from a sharepoint list and I'm getting an error for some reason, as shown below: Error Screen shot

my code for tsx file is given below:

import * as React from 'react';
import styles from './HelloWorld.module.scss';
import { IHelloWorldProps } from './IHelloWorldProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { BookLibListItem } from './BookLibListItem';
import { IBookLibCollection } from './IBookLibCollection';


import { ISPHttpClientOptions, SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';


import {
  TextField,
  autobind,
  PrimaryButton,
  DetailsList,
  DetailsListLayoutMode,
  CheckboxVisibility,
  SelectionMode,
  Dropdown,
  DatePicker,
  IDatePickerStrings,
  IDropdown,
  IDropdownOption,
  ITextFieldStyles,
  IDropdownStyles,
  DetailsRowCheck,
  Selection,
  DayOfWeek
} from 'office-ui-fabric-react';



const DayPickerStrings: IDatePickerStrings = {
  months: [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
  ],

  shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],

  days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],

  shortDays: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],

  goToToday: 'Go to today',
  prevMonthAriaLabel: 'Go to previous month',
  nextMonthAriaLabel: 'Go to next month',
  prevYearAriaLabel: 'Go to previous year',
  nextYearAriaLabel: 'Go to next year',
  closeButtonAriaLabel: 'Close date picker'
};
/*
export interface IDatePickerBasicExampleState {
  firstDayOfWeek?: DayOfWeek;
}
*/

 // Configure the columns for the DetailsList component
 let _bookListColumns = [
  {
    key: 'ID',
    name: 'ID',
    fieldName: 'ID',
    minWidth: 50,
    maxWidth: 100,
    isResizable: true
  },
  {
    key: 'Title',
    name: 'Title',
    fieldName: 'Title',
    minWidth: 50,
    maxWidth: 100,
    isResizable: true
  },
  {
    key: 'Author',
    name: 'Author',
    fieldName: 'Author',
    minWidth: 50,
    maxWidth: 100,
    isResizable: true
  },
  {
    key: 'Publisher',
    name: 'Publisher',
    fieldName: 'Publisher',
    minWidth: 50,
    maxWidth: 100,
    isResizable: true
  },
  {
    key: 'DateOfPublish',
    name: 'DateOfPublish',
    fieldName: 'DateOfPublish',
    minWidth: 50,
    maxWidth: 100,
    isResizable: true
  },
  {
    key: 'ISBN',
    name: 'ISBN',
    fieldName: 'ISBN',
    minWidth: 50,
    maxWidth: 150,
    isResizable: true
  }  
];

const textFieldStyles: Partial<ITextFieldStyles> = { fieldGroup: { width: 300 } };
const narrowTextFieldStyles: Partial<ITextFieldStyles> = { fieldGroup: { width: 100 } };
const narrowDropdownStyles: Partial<IDropdownStyles> = { dropdown: { width: 300 } };

function SetDate(props) {
  //const firstDayOfWeek = React.useState(DayOfWeek.Sunday);
  
  return (<DatePicker
{...props.value && moment(props.value).isValid() ? { value: moment(props.value).toDate() } : {}}
className={css(styles.dateFormField, 'dateFormField')}
placeholder="Select a date..."
isRequired={props.Required}
ariaLabel={props.Title}
parseDateFromString={(dateStr?: string) => { return moment(dateStr, 'L').toDate(); }}
formatDate={(date: Date) => (typeof date.toLocaleDateString === 'function') ? date.toLocaleDateString(locale as any) : ""}
strings={DayPickerStrings}
allowTextInput={true}
onSelectDate={(date) => {
  if (date) props.valueChanged(date.toISOString());
  else props.valueChanged('');
}}
/>);

  ;
}

function GetDate(props){
  const value = (props.value && moment(props.value).isValid()) ? moment(props.value,"YYYY-MM-DD").format('L') : '';
  return <FieldDateRenderer text={value} />;
}

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

  private _selection: Selection;
  //firstDayOfWeek = React.useState(DayOfWeek.Sunday);

  
  
  private _onItemsSelectionChanged = () => {
    
    
    this.setState({
      BookListItem: (this._selection.getSelection()[0] as BookLibListItem)
    });
  }

  private _getListItems(): Promise<BookLibListItem[]> {
    const url: string = this.props.siteUrl + "/_api/web/lists/getbytitle('Book%20Library')/items";
    return this.props.context.spHttpClient.get(url,SPHttpClient.configurations.v1)
    .then(response => {
      console.log(response);
    return response.json();
    })
    .then(json => {
    return json.value;
    }) as Promise<BookLibListItem[]>;
    }
    public bindDetailsList(message: string) : void {

      this._getListItems().then(listItems => {
        console.log(listItems);
        this.setState({ BookListItems: listItems,status: message});
      });
    }
  
    public componentDidMount(): void {
      this.bindDetailsList("All Records have been loaded Successfully");  
  
      
    }

    @autobind
  public btnAdd_click(): void {  

    const url: string = this.props.siteUrl + "/_api/web/lists/getbytitle('Book%20Library')/items";          
       
       const spHttpClientOptions: ISPHttpClientOptions = {
      "body": JSON.stringify(this.state.BookListItem)
    };

    this.props.context.spHttpClient.post(url, SPHttpClient.configurations.v1, spHttpClientOptions)
    .then((response: SPHttpClientResponse) => {
     
      if (response.status === 201) {
        this.bindDetailsList("Record added and All Records were loaded Successfully");         

       
       
      } else {
        let errormessage: string = "An error has occured i.e.  " + response.status + " - " + response.statusText;
        this.setState({status: errormessage});        
      }
    });
  }

   @autobind
  public btnUpdate_click(): void {

    let id: number = this.state.BookListItem.Id;

    const url: string = this.props.siteUrl + "/_api/web/lists/getbytitle('Book%20Library')/items(" + id + ")";          
      
    
    const headers: any = {
      "X-HTTP-Method": "MERGE",
      "IF-MATCH": "*",
    };
       
       const spHttpClientOptions: ISPHttpClientOptions = {
        "headers": headers,
        "body": JSON.stringify(this.state.BookListItem)
    };

    this.props.context.spHttpClient.post(url, SPHttpClient.configurations.v1, spHttpClientOptions)
    .then((response: SPHttpClientResponse) => {
     
      if (response.status === 204) {
        this.bindDetailsList("Record Updated and All Records were loaded Successfully");                
       
      } else {
        let errormessage: string = "An error has occured i.e.  " + response.status + " - " + response.statusText;
        this.setState({status: errormessage});        
      }
    });
  }

  @autobind
  public btnDelete_click(): void {
    let id: number = this.state.BookListItem.Id;

    

    const url: string = this.props.siteUrl + "/_api/web/lists/getbytitle('Book%20Library')/items(" + id + ")";          

    
    const headers: any = { "X-HTTP-Method": "DELETE", "IF-MATCH": "*" };

    const spHttpClientOptions: ISPHttpClientOptions = {
      "headers": headers
    };


    this.props.context.spHttpClient.post(url, SPHttpClient.configurations.v1, spHttpClientOptions)
    .then((response: SPHttpClientResponse) => {
      if (response.status === 204) {
        alert("record got deleted successfully....");
        this.bindDetailsList("Record deleted and All Records were loaded Successfully");   
        
      } else {
        let errormessage: string = "An error has occured i.e.  " + response.status + " - " + response.statusText;
        this.setState({status: errormessage}); 
      }
    });
  }

  constructor(props: IHelloWorldProps, state: IBookLibCollection){
    super(props);

    this.state = {

      status: 'Ready',
      BookListItems: [],
      BookListItem:{
        Id: 0,
        Title: "",
        Author: "",
        Publisher: "",
        DateOfPublish: new Date,
        ISBN: 0
      },

    };

    this._selection = new Selection({
      onSelectionChanged: this._onItemsSelectionChanged,
    });

    
  }



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

    const dropdownRef = React.createRef<IDropdown>();

    return (

      <div className={ styles.helloWorld }>
     
      <TextField                  
      label="ID"
      required={ false } 
      value={ (this.state.BookListItem.Id).toString()}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.Id=e;}}
    />
    <TextField                  
      label="Title"
      required={ true } 
      value={ (this.state.BookListItem.Title)}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.Title=e;}}
    />
    <TextField                  
      label="Author"
      required={ true } 
      value={ (this.state.BookListItem.Author)}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.Author=e;}}
    />
    <TextField                  
      label="Publisher"
      required={ true } 
      value={ (this.state.BookListItem.Publisher)}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.Publisher=e;}}
    />
   <SetDate 
       value={this.state.BookListItem.DateOfPublish} 
       Required={true}
       Title="DateOfPublish"
       //firstDayOfWeek={firstDayOfWeek} 
    /> 
    
    <TextField                  
      label="ISBN"
      required={ true } 
      value={ (this.state.BookListItem.ISBN.toString())}
      styles={textFieldStyles}
      onChanged={e => {this.state.BookListItem.ISBN=e;}}
    />
    

<p className={styles.title}>
         <PrimaryButton
          text='Add'      
          title='Add'              
          onClick={this.btnAdd_click}
        />

        <PrimaryButton
          text='Update'                    
          onClick={this.btnUpdate_click}
        />

        <PrimaryButton
          text='Delete'                    
          onClick={this.btnDelete_click}
        />
        
      </p>


    <div id="divStatus">
      {this.state.status}
    </div>

    <div>
    <DetailsList
          items={ this.state.BookListItems}
          columns={ _bookListColumns }
          setKey='Id'
          checkboxVisibility={ CheckboxVisibility.onHover}
          selectionMode={ SelectionMode.single}
          layoutMode={ DetailsListLayoutMode.fixedColumns }
          compact={ true }
          selection={this._selection}                                         
      />
      </div>  


</div>


    );
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

I'm using the following interface as the data model for the list in SharePoint:

export interface BookLibListItem {
Id: number;
Title: string;
Author: string;
Publisher: string;
DateOfPublish: Date;
ISBN: number;

}

Appreciate any advice regarding this issue.

Mudasir Sahto
  • 69
  • 3
  • 13

2 Answers2

0

Dates in Sharepoint can be tricky (especially with multiple localization) - therefor I am using moment to format date. For DatePicker, I am using this code (with moment library):

<DatePicker
{...props.value && moment(props.value).isValid() ? { value: moment(props.value).toDate() } : {}}
className={css(styles.dateFormField, 'dateFormField')}
placeholder={strings.FormFields.DateFormFieldPlaceholder}
isRequired={props.fieldSchema.Required}
ariaLabel={props.fieldSchema.Title}
parseDateFromString={(dateStr?: string) => { return moment(dateStr, 'L').toDate(); }}
formatDate={(date: Date) => (typeof date.toLocaleDateString === 'function') ? date.toLocaleDateString(locale) : ''}
strings={strings.FormFields}
firstDayOfWeek={props.fieldSchema.FirstDayOfWeek}
allowTextInput={true}
onSelectDate={(date) => {
  if (date) props.valueChanged(date.toISOString());
  else props.valueChanged('');
}}

Code above is separate component to render a Date Picker. To save a date it shouldbe in ISO format (function date.toISOString()).

To display date I am using FieldDateRenderer - import { FieldDateRenderer } from "@pnp/spfx-controls-react/lib/FieldDateRenderer";

const value = (props.value && moment(props.value).isValid()) ? moment(props.value,"YYYY-MM-DD").format('L') : '';
return <FieldDateRenderer text={value} />;
Matej
  • 396
  • 1
  • 9
  • 1
    Let me know if you need any additional help. – Matej Mar 23 '21 at 06:39
  • Thanks Matej. I have tried applying your code and achieved some progress, that is, I was able to get to display the date in the Details list view. However, I still am getting some issues in storing the date value and displaying the date value in a format. – Mudasir Sahto Apr 11 '21 at 07:21
  • I have modified the code based on your recommendation and using the moment library js for date formatting, I have included two functions, named SetDate() and GetDate() respectively. I have not created a separate component for this and I defined these under the same tsx file. I'm rendering the date picker control using the tag. – Mudasir Sahto Apr 11 '21 at 07:37
0

How are you reading and writing to lists? Are you writing your own requests, or are you using a library like @pnp/sp (pnpjs)? I would recommend pnpjs if you're not using it in the first instance, check the Getting Started Guide.

The Fluent UI / office-ui-fabric-react <DatePicker/> takes Date objects as a value and outputs the same when a value is changed. When you get a date back from SharePoint, it will likely be represented as an ISO string, you can parse this to a Date object with:

const date = new Date(columnValueFromSharePoint);

You don't need additional libraries like moment for this. I would only recommend moment in this instance if you need to do some kind of complex timezone adjustment or date manipulation. When you need to do something simple like add a few minutes, or hours, or days or months you can do those quite simply with the standard primitive Date methods.

Whenever you need to write a Date back to SharePoint, you can output your date as an ISO string:

const result = await sp.web.lists.getByTitle('mylist').items.add({
   Title: 'New Item',
   MyDateColumn: date.toISOString()
});

Fluent UI doesn't (yet) include a Date and Time picker, but the @pnp/spfx-controls-react project does. Like DatePicker, it takes a standard Date object as a value and outputs the same in its onChange event. Meaning you can easily convert it to an ISO string when you need to populate SharePoint columns.

Reference for manipulating Date objects in JavaScript generally is available here on the MDN Web Docs

t0mgerman
  • 186
  • 2
  • 8