Filter SPFX ListView by Date range (using SPFX Reusable Controls DateTimePicker)

Most of my users like to have their list data filtered by Date range (a start date and end date). Some were quite happy with SharePoint List Views in case they have a set of predefined weekly, monthly views. But some users want to filter in custom date range which I wanted to create this using fantastic SPFX reusable controls. Thanks for contributors.

What is used?

  • SPFX reusable controls “ListView
  • SPFX reusable controls “DateTimePicker
  • Office UI Fabric react control “Button
  • PNPJS to get user profile details

Step 1: Create SPFX React webpart

Step 2: Install and Import controls

I have used the PnP SPFX reusable react controls Listview. Install it with the below command into the project terminal

npm install @pnp/spfx-controls-react --save --save-exact

I have also used a Button which is from Office UI Fabric React controls, so we need to install the below one. I love using OUIFRC but you can use even a simple button.

npm install office-ui-fabric-react@5.132.0 –save

Now we need to import them into our webpart, so open “.tsx” file and add this import statement just below the default imports

import { ListView, IViewField, SelectionMode} from "@pnp/spfx-controls-react/lib/ListView";
import { DateTimePicker, DateConvention } from '@pnp/spfx-controls-react/lib/dateTimePicker';
import { PrimaryButton } from 'office-ui-fabric-react';
import { SPHttpClient } from '@microsoft/sp-http';

Add the below html tags into the render method inside return section to add ListView, DateTimePicker and Button controls to the webpart

<div className={styles.listview}>
            <div className={styles.container}>
              <br></br>
              <div className={styles.row2}>
                <div className={styles.column2}>
                <label style={{padding: "30px", verticalAlign: "middle"}}>Start Date : </label>
                </div>
                <div className={styles.column2}>
                <DateTimePicker
                  dateConvention={DateConvention.Date} 
                  isMonthPickerVisible={false} 
                  showGoToToday={false} 
                  formatDate={this._onFormatDate} 
                  onChange={this._onSelectStartDate}   
                  value={this.state.fromDate}    
                  showLabels={false}           
                  />
                </div>
                <div className={styles.column2}>
                <label style={{padding: "30px", verticalAlign: "middle"}}>End Date : </label>
                </div>
                <div className={styles.column2}>
                <DateTimePicker  
                  dateConvention={DateConvention.Date} 
                  isMonthPickerVisible={false} 
                  showGoToToday={false} 
                  formatDate={this._onFormatDate} 
                  onChange={this._onSelectEndDate}  
                  value={this.state.toDate}   
                  showLabels={false}             
                  />                  
                </div>
                <div className={styles.column2}>    
                <PrimaryButton text="Filter" onClick={this._filterClicked} disabled={false} checked={false} />
                </div>
              </div>
              <div>
                <br></br>
                <h2>Reusable Listview</h2>
                <ListView
                items={this.state.items}
                viewFields={viewFields}
                iconFieldName="ServerRelativeUrl"
                compact={false}
                selectionMode={SelectionMode.multiple}
                selection={this._getSelection}
                showFilter={true}
                filterPlaceHolder="Search..." />
              </div>            
            </div>
            </div>

Now you need do decide the columns to display in our ListView webpart, so add these just above the return section

const viewFields: IViewField[] = [
      {
        name: 'Title',
        displayName: 'Title',
        sorting: true,
        maxWidth: 80
      },
      {
        name: 'Requestor',
        displayName: 'Requestor',
        sorting: true,
        maxWidth: 80
      },
      {
        name: 'RequestDate',
        displayName: "Request Date",
        sorting: true,
        maxWidth: 80
      },
      {
        name: 'Location',
        displayName: "Location",
        sorting: true,
        maxWidth: 80
      },
      {
        name: 'Status',
        displayName: "Status",
        sorting: true,
        maxWidth: 80
      }
    ];

Step 3: Declare and Intialize State

Create an interface to declare the State variables in the .tsx file just below the default import tags

export interface IListviewState {
  items: any[];
  fromDate?: Date | null;
  toDate?: Date | null;
  fromDateStr: string;
  toDateStr: string;
}

Later initialize them inside using an constructor method inside the class method

constructor(props: IListviewProps, state: IListviewState) {
    super(props);
    let cdate = new Date();
    let dd= (cdate.getFullYear()) +'-'+(cdate.getMonth() + 1) + '-' + cdate.getDate()+'T00:00:00';
    this.state = {
      items: [],
      fromDate: new Date(),
      toDate: new Date(),
      fromDateStr: dd,
      toDateStr: dd,
    };
  }

Step 4: Add Events

Open the .tsx file and add these functions into the class and just below the render method closes. These functions are required for Date change, Filter Button and to load List data.

private _getSelection(items: any[]) {
    console.log('Selected items:', items);
  }
  private _onFormatDate = (date: Date): string => {
    return (date.getMonth() + 1) + '/' + date.getDate() + '/' + (date.getFullYear());
  };
  //Called on Start Date change and to set State
  private _onSelectStartDate = (date1: Date | null | undefined): void => {
    //console.log("Date selected"+ (date.getFullYear()) +'-'+(date.getMonth() + 1) + '-' + date.getDate()+'T00:00:00'); 
    var strFromDate: string;
    strFromDate= (date1.getFullYear()) +'-'+(date1.getMonth() + 1) + '-' + date1.getDate()+'T00:00:00';
    this.setState({ fromDate:date1, fromDateStr: strFromDate}, () => console.log(this.state.fromDate + "  "+this.state.fromDateStr));
  };
  //Called on End Date change and to set State
  private _onSelectEndDate = (date2: Date | null | undefined): void => {
    var strToDate: string;
    strToDate= (date2.getFullYear()) +'-'+(date2.getMonth() + 1) + '-' + date2.getDate()+'T00:00:00';
    this.setState({ toDate:date2, toDateStr: strToDate}, () => console.log(this.state.toDate + "  "+this.state.toDateStr));
  };
  //Called when filter button is clicked
  public _filterClicked = ()=> {
    this._loadList();
  }
  //To retrieve SP List data
  private _loadList():void {
    const restApi = `${this.props.context.pageContext.web.absoluteUrl}/_api/web/lists/GetByTitle('Requests')/items?$filter=(RequestDate%20ge%20datetime'${this.state.fromDateStr}' and RequestDate le datetime'${this.state.toDateStr}')`;
    this.props.context.spHttpClient.get(restApi, SPHttpClient.configurations.v1)
      .then(resp => { return resp.json(); })
      .then(items => {
        this.setState({
          items: items.value ? items.value : []
        });
      });
  }

Add these React methods just below the constructor method. “ComponentDidMount” is called on load and “componentDidUpdate” will be called when there is change in State (when dates are changed and filter is clicked)

public componentDidMount() {
    console.log(this.state.fromDate);
    //const restApi = `${this.props.context.pageContext.web.absoluteUrl}/_api/web/lists/GetByTitle('Requests')/items?$filter=(RequestDate%20ge%20datetime%272020-01-04T00:00:00%27 and RequestDate%20le%20datetime%272020-01-04T00:00:00%27)`;    
    this._loadList(); 
  }
  public componentDidUpdate(prevProps: IListviewProps, prevState: IListviewState, prevContext: any): void {
    if ((this.state.fromDateStr !== prevState.fromDateStr) || (this.state.toDateStr !== prevState.toDateStr)) {
      //this._loadList();      
    }
  }

Open “Listview.module.scss” and add the below styles under “listview” tag

.column2 {
    float: left;
    width: 20%;
  }  
  /* Clear floats after the columns */
  .row2 {
    content: "";
    display: table;
    clear: both;
  }

Step 6: Add context

Open the “IListviewProps.ts” update them as below

import { WebPartContext } from '@microsoft/sp-webpart-base';
export interface IListviewProps {
  context: WebPartContext;
}

Open “ListviewWebPart.ts”, import the pnp/sp

import { sp } from "@pnp/sp";

add this onInit method to set the context

public onInit(): Promise<void> {
    return super.onInit().then(_ => {
    sp.setup({
    spfxContext: this.context
    });
    });
    }

Step 7: Deploy

Finally, we are ready to deploy with our webpart. Let’s package it to move to the site.

gulp build
gulp bundle --ship
gulp package-solution --ship

Get the SPFX package file from “SharePoint\solution” and upload it to your app catalogue site We are ready to add our webpart into a SharePoint page. Create a page in your SharePoint online site, add the webpart into it and publish the page. My webpart loads my list data filtered with only today’s requests.