Paging, Grouping, Sorting hangs application

Hi, 

I've got a problem. When I implemented my own CRUD actions via FetchAPI all other actions like Paging, Grouping or Sorting hangs the app. Whenever I try to use it, the loader_circle shows and stays forever and I need to restart app.

App is written in Next JS.


Help, please.


Here's my code:

import type { GetServerSideProps, NextPage } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { Fragment, useEffect, useState } from "react";
import {
LocalStorageUserData,
NextPageWithLayout,
PlantVariety,
} from "../types/typings";
import { v4 as uuidv4 } from "uuid";

import {
GridComponent,
ColumnsDirective,
ColumnDirective,
Resize,
Page,
Edit,
Inject,
Toolbar,
ToolbarItems,
Group,
EditSettingsModel,
Sort,
} from "@syncfusion/ej2-react-grids";

import Header from "../components/Header";
import MainLayout from "../components/MainLayout";
import Loading from "../components/Loading";

const isBrowser = () => typeof window !== "undefined";

export const getServerSideProps: GetServerSideProps = async ({ locale }) => {
try {
////i18n passing language
const response = {
props: {
...(await serverSideTranslations(localeWellTyped as string, ["common"])),
},
};

return response;
} catch (error) {
const response = {
props: {},
};
return response;
}
};

interface PlantVarietyFetchedData {
result: PlantVariety[];
count: number;
}

const PlantVarieties: NextPageWithLayout = () => {
////vars
const [plantsVarietes, setPlantsVarietes] = useState<
PlantVarietyFetchedData | {}
>({});

//fetchingData
let apiUrl = "";
let token = "";
let userId = "";
if (isBrowser()) {
if (localStorage.getItem("userData") !== "undefined") {
const localStorageAsObject: LocalStorageUserData = JSON.parse(
localStorage.getItem("userData") as string
);
token = localStorageAsObject.token;
userId = localStorageAsObject.userId;
}
}

//GET
const refreshData = async () => {
apiUrl = `${process.env.NEXT_PUBLIC_API_BASE_ADDRESS}/api/plant-varieties/${userId}`;

fetch(apiUrl, {
headers: {
Authorization: `${token}`,
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data) => {
setPlantsVarietes(data as PlantVarietyFetchedData);
});
};

useEffect(() => {
refreshData();
}, []);

const editOptions: EditSettingsModel = {
allowAdding: true,
allowEditing: true,
allowDeleting: true,
mode: "Dialog",
};
const toolbarOptions: ToolbarItems[] = ["Add", "Edit", "Delete"];

function dataStageChanged(state: any) {
console.log(state);
}

async function dataSourceChanged(state: any) {
const newPlantId = uuidv4();
const dataToBePosted = { ...state.data };
dataToBePosted.plantId = newPlantId;

//POST
if (state.action === "add" && state.requestType === "save") {
return fetch(
`${process.env.NEXT_PUBLIC_API_BASE_ADDRESS}/api/plant-varieties/${userId}`,
{
method: "POST",
headers: {
Authorization: `${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(dataToBePosted),
}
)
.then((data) => {
return data;
})
.then((res) => {
state.endEdit();
refreshData();
});
}

//PUT
if (state.action === "edit" && state.requestType === "save") {
return fetch(
`${process.env.NEXT_PUBLIC_API_BASE_ADDRESS}/api/plant-varieties/${userId}`,
{
method: "PUT",
headers: {
Authorization: `${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(state.data),
}
)
.then((data) => data)
.then((res) => {
state.endEdit();
refreshData();
});
}

//DELETE
if (!state.action && state.requestType === "delete") {
return fetch(
`${process.env.NEXT_PUBLIC_API_BASE_ADDRESS}/api/plant-varieties/${userId}?plantId=${state.data[0].plantId}`,
{
method: "DELETE",
headers: {
Authorization: `${token}`,
"Content-Type": "application/json",
},
}
)
.then((data) => {
return data;
})
.then((res) => refreshData());
}
}

return (
<Fragment>
<div className="m-2 md:m-10 p-2 md:p-10 bg-white rounded-3xl">
<Header category="Monitoring" title="Odmiany" />
<GridComponent
dataSource={plantsVarietes}
allowSorting={true}
allowGrouping
allowPaging
pageSettings={{ pageSize: 10 }}
toolbar={toolbarOptions}
editSettings={editOptions}
dataSourceChanged={dataSourceChanged}
>
<ColumnsDirective>
<ColumnDirective
field="name"
headerText="Nazwa"
textAlign="Center"
width="100%"
/>
<ColumnDirective
field="varietyCode"
headerText="Kod odmiany"
textAlign="Center"
width="200"
/>
ColumnsDirective>
<Inject services={[Resize, Edit, Toolbar, Sort, Page, Group]} />
GridComponent>
div>
Fragment>
);
};

PlantVarieties.getLayout = function getLayout(page: typeof PlantVarieties) {
return <MainLayout>{page}MainLayout>;
};
export default PlantVarieties;


3 Replies

RS Rajapandiyan Settu Syncfusion Team July 22, 2022 05:40 PM UTC

Hi Piotr,


Thanks for contacting Syncfusion support.


Before proceeding with your query, kindly share the below details to validate further.


  1. Share the video demo of the reported problem (highly recommended).
  2. Share the package.json file.
  3. Remove the refreshData() method used in the dataSourceChanged event and check the reported behavior again.
  4. Did the state.endEdit() method executed properly in the dataSourceChanged event after the save action? Please debug the code and ensure this.
    https://ej2.syncfusion.com/vue/documentation/grid/data-binding/data-binding/#perform-crud-operations
  5. Are you facing this issue only after upgrading the Syncfusion packages?


Regards,

Rajapandiyan S



PK Piotr Kozlowski August 2, 2022 06:55 PM UTC

hi,


I had some delay. I thought it was NEXT.JS problem so I rewrote it into React ... but problem hasn't disappeared. So here's info you wanted:


package.json:

{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@syncfusion/ej2": "^20.2.40",
"@syncfusion/ej2-react-charts": "^20.2.40",
"@syncfusion/ej2-react-grids": "^20.2.40",
"@syncfusion/ej2-react-popups": "^20.2.40",
"@tanstack/react-query": "^4.0.10",
"@tanstack/react-query-devtools": "^4.0.10",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.11.45",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-transition-group": "^4.4.5",
"@types/uuid": "^8.3.4",
"@types/yup": "^0.29.14",
"axios": "^0.27.2",
"buffer": "^6.0.3",
"formik": "^2.2.9",
"i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4",
"i18next-http-backend": "^1.4.1",
"jsonwebtoken": "^8.5.1",
"jwt-decode": "^3.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^11.18.1",
"react-icons": "^4.4.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"react-spinners": "^0.13.3",
"react-transition-group": "^4.4.2",
"typescript": "^4.7.4",
"uuid": "^8.3.2",
"web-vitals": "^2.1.4",
"yup": "^0.32.11"
},
"scripts": {
"start": "react-scripts start",
"dev": "i18nexus pull -k asP18t3Jst04ZrBvzmAHtA && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"autoprefixer": "^10.4.7",
"postcss": "^8.4.14",
"tailwindcss": "^3.1.6"
}
}



newly created component in React:

import React, { Fragment, useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { useTranslation } from "react-i18next";
import { useThemeProvider } from "../contexts/theme-context";
import {
GridComponent,
ColumnsDirective,
ColumnDirective,
Resize,
Page,
Edit,
Inject,
Toolbar,
ToolbarItems,
Group,
EditSettingsModel,
Sort,
} from "@syncfusion/ej2-react-grids";
import {
useDeletePlantVarieties,
useGetPlantVarieties,
} from "../hooks/usePlantVarietiesCRUDData";
import { usePostPlantVarieties } from "../hooks/usePlantVarietiesCRUDData";
import { usePutPlantVarieties } from "../hooks/usePlantVarietiesCRUDData";

import Header from "../components/Header";
import { PlantVariety } from "../utils/types/app.types";

const PlantVarieties = () => {
////vars
const { t } = useTranslation();
const { currentColor } = useThemeProvider();
const [plantVarieties, setPlantVarieties] = useState<
{ result: PlantVariety[]; count: number } | undefined
>(undefined);

////CRUD
//get
const { data, isFetching, isError, error } = useGetPlantVarieties();
useEffect(() => {
if (data) setPlantVarieties({ ...data.data });
}, [data]);
//post/put
const { mutate: postMutate } = usePostPlantVarieties();
const { mutate: putMutate } = usePutPlantVarieties();
const { mutate: deleteMutate } = useDeletePlantVarieties();

////grid options
const editOptions: EditSettingsModel = {
allowAdding: true,
allowEditing: true,
allowDeleting: true,
mode: "Dialog",
};
const toolbarOptions: ToolbarItems[] = ["Add", "Edit", "Delete"];

////
async function dataSourceChanged(state: any) {
if (state.action === "add") {
console.log("add");
postMutate(createPlantObject(state.data.varietyCode, state.data.name));
state.endEdit();
}
if (state.action === "edit") {
console.log("edit");
putMutate(state.data);
state.endEdit();
}
if (state.requestType === "delete") {
console.log("delete");
deleteMutate(state.data[0].plantId);
state.endEdit();
}
}

////utils
function createPlantObject(varietyCode: string, name: string) {
const newPlantId = uuidv4();
const plant: PlantVariety = {
plantId: newPlantId,
varietyCode: varietyCode,
name: name,
};
return plant;
}

////jsx
let content = (
<div className="m-2 md:m-10 p-2 md:p-10 bg-white rounded-3xl">
<Header category={t("common:monitoring")} title={t("common:varietes")} />
<div className="font-loading animate-pulse">some dummy text div>
<div className="font-loading animate-pulse">
somevery verydummydummytemporarytext text{" "}
div>
<div className="font-loading animate-pulse">some dummy text div>
div>
);
if (plantVarieties) {
content = (
<Fragment>
<div className="m-2 md:m-10 p-2 md:p-10 bg-white rounded-3xl">
<Header
category={t("common:monitoring")}
title={t("common:varietes")}
/>
<GridComponent
dataSource={plantVarieties}
allowSorting={true}
allowGrouping
allowPaging
pageSettings={{ pageSize: 10 }}
toolbar={toolbarOptions}
editSettings={editOptions}
dataSourceChanged={dataSourceChanged}
>
<ColumnsDirective>
<ColumnDirective
field="name"
headerText="Nazwa"
textAlign="Center"
width="100%"
/>
<ColumnDirective
field="varietyCode"
headerText="Kod odmiany"
textAlign="Center"
width="200"
/>
ColumnsDirective>
<Inject services={[Resize, Edit, Toolbar, Page, Group, Sort]} />
GridComponent>
div>
Fragment>
);
}
return content;
};

export default PlantVarieties;


CRUD operations hook:

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import { getDataFromLocalStorage } from "../utils/localStorageUtils";
import { PlantVariety } from "../utils/types/app.types";

const dataFromLocalStorage = getDataFromLocalStorage();
const token = dataFromLocalStorage ? dataFromLocalStorage.token : "";

interface PlantVarietiesFetched {
result: PlantVariety[];
count: number;
}

////GET
const getPlantVarieties = () =>
axios.get<PlantVarietiesFetched>(
`${process.env.REACT_APP_BACKEND_URL}/api/plant-varieties/`,
{
headers: {
Authorization: token,
Accept: "application/json",
},
}
);

export const useGetPlantVarieties = () => {
return useQuery(["plantVarieties"], () => getPlantVarieties());
};

////POST
const postPlantVarieties = (plantData: PlantVariety) =>
axios.post(
`${process.env.REACT_APP_BACKEND_URL}/api/plant-varieties/`,
plantData,
{
headers: {
Authorization: token,
Accept: "application/json",
},
}
);

export const usePostPlantVarieties = () => {
const queryClient = useQueryClient();
return useMutation(postPlantVarieties, {
onSuccess: () => {
queryClient.invalidateQueries(["plantVarieties"]);
},
});
};

////PUT
const putPlantVarieties = (plantData: PlantVariety) =>
axios.put(
`${process.env.REACT_APP_BACKEND_URL}/api/plant-varieties/`,
plantData,
{
headers: {
Authorization: token,
Accept: "application/json",
},
}
);

export const usePutPlantVarieties = () => {
const queryClient = useQueryClient();
return useMutation(putPlantVarieties, {
onSuccess: () => {
queryClient.invalidateQueries(["plantVarieties"]);
},
});
};

////DELETE
const deletePlantVarieties = (plantId: string) =>
axios.delete(
`${process.env.REACT_APP_BACKEND_URL}/api/plant-varieties/${plantId}`,
{
headers: {
Authorization: token,
Accept: "application/json",
},
}
);

export const useDeletePlantVarieties = () => {
const queryClient = useQueryClient();
return useMutation(deletePlantVarieties, {
onSuccess: () => {
queryClient.invalidateQueries(["plantVarieties"]);
},
});
};

...


video:

https://res.cloudinary.com/dn8l30dkf/video/upload/v1659453004/2022-08-02_17h06_34_xj0xav.mp4

all CRUD actions work without problem (as seen on video),
but any click in Paging, sorting by clicking on Column Header, or dragging column header to group it - end with endless spinner, and action never finishes.


regards

Piotr



RS Rajapandiyan Settu Syncfusion Team August 3, 2022 01:47 PM UTC

Hi Piotr,


Thanks for your update.


In your Grid, you are using custom data-binding feature to bind the data to Grid. We would like to share the behavior of custom-binding in the EJ2 Grid.


For every grid action (such as FilterPage, etc.,), we have triggered the dataStateChange event and, in that event arguments we have sent the corresponding action details(like skip, take, filter field, value, sort direction, etc.,) based on that, you can perform the action in your service and return the data as a result and count object. 


Note: ‘dataStateChange’ event is not triggered at the Grid initial render. If you are using a remote service, you need to call your remote service manually with a pagination query (need to set skip value as 0 and take value based on your pageSize of pageSettings in Grid. If you are not defined pageSize in pageSettings, you need to send the default value 12 ) in mounted. Please return the result as “{result: […], count: …}” format
to Grid.


dataStateChange: https://ej2.syncfusion.com/vue/documentation/api/grid/#datastatechange


 

[App.vue]

 

    dataStateChange: function (state) {

      if (state.action &&

        (state.action.requestType === "filterchoicerequest" ||

          state.action.requestType === "filtersearchbegin" ||

          state.action.requestType === "stringfilterrequest")) {

        // below code will executed when

        // 1. opening the Excel Filter dialog and Menu filter dialog,

        // 2. searching the value in excel filter dialog and

 

        this.orderService.execute(state).then((gridData) => {

          state.dataSource(gridData.result); // bind array of Objects to the Grid

        });

      } else {

        // Handled all the other Grid actions like paging, sorting etc.. by using dataState change event

        this.orderService.execute(state).then((gridData) => {

          this.data = gridData;

        });

      }

    }

 


dataSourceChanged’ event is triggered when performing CRUD action in Grid. You can perform the CRUD action in your service using action details from this event and, you need to call the endEdit method to indicate the completion of save operation.


Custom-binding: https://ej2.syncfusion.com/vue/documentation/grid/data-binding/data-binding/#custom-binding


Demo: https://ej2.syncfusion.com/vue/demos/#/material/grid/custom-binding.html


We have prepared a simple sample for your reference.


Sample: https://codesandbox.io/s/customdatabinding-forked-s18bxn?file=/src/App.vue


Please get back to us if you need further assistance.


Regards,

Rajapandiyan S


Loader.
Up arrow icon