Search code examples
reactjsreduxsyncfusion

How to get SyncFusion grid with custom binding to show/hide spinner


I have a SyncFusion grid that is using custom binding and I'm having two issues. Using React v18 with Redux.

  1. When initially requesting the data to populate the grid, it is not showing the loading spinner, even though I have set it up via a side-effect and a Redux state property (isLoading) to do so. Via the console logs I can see that the side-effects are running as intended, but doesn't show spinner.

  2. Once the initial data request comes back and populates the grid the spinner appears and doesn't stop. I believe it has something to do with the row-detail templates that are being added. If I remove the detail template the spinner does not appear. I have added in a hideSpnner to my external columnChooser button, after I click this, everything works normally.

It's not appearing when I want it to, then appearing and not going away.

Once I'm past this initial data request and force the hideSpinner() via the external column chooser button, subsequent data requests work fine when paging and sorting, spinner shows appropriately.

Not sure if there is a community of SyncFusion users here, but hopefully someone can help.

Here is my slice:

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { DataStateChangeEventArgs } from "@syncfusion/ej2-react-grids";
import { ServiceRequest } from "./models/ServiceRequest.interface";
import { ServiceRequestResult } from "./models/ServiceRequestResult.interface";
import csmService from "./services/csmMyRequestService";

interface AsyncState {
  isLoading: boolean;
  isSuccess: boolean;
  isError: boolean;
}

interface MyRequestState extends AsyncState {
  result: ServiceRequest[];
  count: number;
}

const initialState: MyRequestState = {
  isLoading: false,
  isSuccess: false,
  isError: false,
  result:[], 
  count: 0
}

export const getMyRequests = createAsyncThunk(
  'csm/getMyRequests',
async (gridCriteria: DataStateChangeEventArgs) => {
  try {
    return await csmService.getMyRequests(gridCriteria);
  } catch (error) {
    console.log('Error: ', error);
  }
});

export const csmMyRequestSlice = createSlice({
  name: 'csmMyRequest',
  initialState,
  reducers: {},
  extraReducers(builder) {
    builder
    .addCase(getMyRequests.pending, (state) => {
      state.isLoading = true;
    })
    .addCase(getMyRequests.fulfilled, (state, action) => {
      
      state.result = action.payload?.myRequests || [];
      state.count = action.payload?.count || 0;

      state.isLoading = false;
      state.isSuccess = true;
    })
    .addCase(getMyRequests.rejected, (state) => {
      
      state.result = [];
      state.count = 0;

      state.isLoading = false;
      state.isError = true;
    })
  },
});

export default csmMyRequestSlice.reducer;

Here is my component:

import { FC, useEffect, useRef, useState } from 'react';
import { Internationalization } from '@syncfusion/ej2-base';
import { ColumnDirective, ColumnsDirective, DataStateChangeEventArgs, Grid, GridComponent } from '@syncfusion/ej2-react-grids';
import { Inject, Page, Sort, Filter, FilterSettingsModel, Resize, ColumnChooser, DetailRow } from '@syncfusion/ej2-react-grids';
import { useAppDispatch, useAppSelector } from '../../../hooks/redux/hooks';
import styles from './MyRequests.component.module.scss';
import { getMyRequests } from '../csmMyRequestSlice';
import { IconButton, styled, Tooltip, tooltipClasses, TooltipProps } from '@mui/material';
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
import { ServiceRequestResult } from '../models/ServiceRequestResult.interface';

let instance = new Internationalization();

const MyRequestsComponent: FC = () => {

  const dispatch = useAppDispatch();

  const { isLoading, result, count, isSuccess } = useAppSelector((state) => state.csmMyRequestReducer);

  let initialMyRequests = { result: [], count: 0 };
  const [myRequests, setMyRequests] = useState<ServiceRequestResult>(initialMyRequests);

  const pageSettings = {
    pageSize: 10,
    pageSizes: ["10", "20", "30", "40", "50"]
  };

  const sortSettings = {
    columns: []
  };

  const columnChooserSettings = {
    hideColumns: [
      "Contact",
      "Request Subtype",
      "Reference",
      "Sys. Logged Date",
      "Sys. Closed Date"
    ]
  };

  let myGridInstanceRef: Grid | null;

  const format = (value: Date) => {
    return instance.formatDate(value, { skeleton: 'yMd', type: 'date' });
  };

  const dataBound = () => {
  }

  const dataStateChange = (gridCriteria: DataStateChangeEventArgs) => {

    if (myGridInstanceRef && gridCriteria.action) {
      const requestType = gridCriteria.action.requestType;

      switch (requestType) {
        case 'paging':
        case 'sorting':
          dispatch(getMyRequests(gridCriteria));
          break;
      }
    }
  };

  
  const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => (
    <Tooltip {...props} classes={{ popper: className }} />
  ))({
    [`& .${tooltipClasses.tooltip}`]: {
      maxWidth: 500,
      fontSize: 13,
      color: 'white',
    },
  });

  function gridDetailTemplate(props: any) {
    return (
      <CustomWidthTooltip title={props.Detail}><p className={`${styles['RequestDetailText']}`}>Detail: {' '}{props.Detail}</p></CustomWidthTooltip>
    );
  }
  let template: any = gridDetailTemplate;

  const columnChooserClick = (event: React.MouseEvent<HTMLElement>) => {
    if (myGridInstanceRef) {
      myGridInstanceRef.hideSpinner(); //Forced hide of spinner here
      myGridInstanceRef.columnChooserModule.openColumnChooser();
    }
  };

  useEffect(() => {
    if (myGridInstanceRef) {
      if (isLoading) {
        console.log('is Loading show spinner'); //Goes through here but spinner doesn't display
        myGridInstanceRef.showSpinner();
      } else {
        console.log('not Loading hide spinner'); //Who knows if it gets hidden as  it never gets displayed
        myGridInstanceRef.hideSpinner();
      }
    }
  }, [isLoading])

  useEffect(() => {
    if (myGridInstanceRef && isSuccess) {
      setMyRequests({ result: result, count: count });
    }
  }, [result, isSuccess])

  useEffect(() => {
    if (myGridInstanceRef) {
      columnChooserSettings.hideColumns.forEach((field) => {
        myGridInstanceRef!.hideColumns(field);
      });

      const gridCriteria = { skip: 0, take: 10 };
      dispatch(getMyRequests(gridCriteria));
    }
  }, [])

  return (
    <div className={`${styles['RequestSection']}`}>
      <legend className={`${styles['RequestLegend']}`}>My Requests:
        <Tooltip title="Show/Hide Columns">
          <IconButton
            className={`${styles['ColumnChooser']}`}
            onClick={columnChooserClick}
            size="small"
          >
            <ViewColumnIcon />
          </IconButton>
        </Tooltip>
      </legend>

      <div className={`${styles['RequestGridContainer']}`}>

        <GridComponent
          ref={(g) => (myGridInstanceRef = g)}
          dataSource={myRequests}
          allowPaging={true} pageSettings={pageSettings}
          allowSorting={true} allowMultiSorting={true} sortSettings={sortSettings}
          allowResizing={true}
          allowReordering={true}
          showColumnChooser={true}
          detailTemplate={template.bind(this)}
          dataBound={dataBound.bind(this)}
          dataStateChange={dataStateChange.bind(this)}
          height='100%'
        >
          <ColumnsDirective>
            <ColumnDirective field='ServiceRequestTag' headerText='Request #' />
            <ColumnDirective field='Caller.Name' headerText='Caller' />
            <ColumnDirective field='Source' />
            <ColumnDirective field='Contact.ContactName' headerText='Contact' />
            <ColumnDirective field='ServiceType.ServiceTypeName' headerText='Service Type' />
            <ColumnDirective field='ServiceRequestType.ServiceRequestTypeName' headerText='Request Type' />
            <ColumnDirective field='ServiceRequestSubtype.ServiceRequestSubtypeName' headerText='Request Subtype' />
            <ColumnDirective field='Poi.Address' headerText='POI Address' />
            <ColumnDirective field='Poi.CityTown' headerText='POI City/Town' />
            <ColumnDirective field='ReferenceNumbers' headerText='Reference' />
            <ColumnDirective field='OwnerName' headerText='Owner' />
            <ColumnDirective field='Status.StatusName' headerText='Status' width='100' />
            <ColumnDirective field='LoggedByName' headerText='Logged By' />
            <ColumnDirective field='LoggedDate' headerText='Logged Date' type='datetime' format='dd MMM yyyy HH:mm' />
            <ColumnDirective field='SystemLoggedDate' headerText='Sys. Logged Date' type='datetime' format='dd MMM yyyy HH:mm' />
            <ColumnDirective field='ClosedByName' headerText='Closed By' />
            <ColumnDirective field='ClosedDate' headerText='Closed Date' type='datetime' format='dd MMM yyyy HH:mm' />
            <ColumnDirective field='SystemClosedDate' headerText='Sys. Closed Date' type='datetime' format='dd MMM yyyy HH:mm' />
            <ColumnDirective field='DueDate' headerText='Due Date' type='datetime' format='dd MMM yyyy HH:mm' />
          </ColumnsDirective>
          <Inject services={[Page, Sort, Resize, ColumnChooser, DetailRow]} />
        </GridComponent>
      </div>
    </div>
  )
}

export default MyRequestsComponent;

Solution

  • Found out that it was the actual grid causing the issue, while the grid control they provide is able to be used with React, it is not very React-ful when dealing with side-effects, they seem quite locked into their vanilla javascript event handlers and anything outside of that causes issues.