From 5cd46adb7446a228b2aa30537ea5035b3a82a78c Mon Sep 17 00:00:00 2001 From: radubrehar Date: Wed, 11 Oct 2023 17:32:14 +0300 Subject: [PATCH] fixes #201 onDataMutations being called all the time after an initial mutation and release version patch --- .../props/data/mutations-new-data.page.tsx | 205 +++++++++++++++++ .../props/data/mutations-new-data.spec.ts | 56 +++++ .../props/data/onDataArrayChange.page.tsx | 212 ++++++++++++++++++ source/src/components/DataSource/Indexer.ts | 2 +- source/src/components/DataSource/index.tsx | 3 +- .../components/DataSource/state/reducer.ts | 4 +- www/content/docs/releases/index.page.md | 4 + 7 files changed, 482 insertions(+), 4 deletions(-) create mode 100644 examples/src/pages/tests/table/props/data/mutations-new-data.page.tsx create mode 100644 examples/src/pages/tests/table/props/data/mutations-new-data.spec.ts create mode 100644 examples/src/pages/tests/table/props/data/onDataArrayChange.page.tsx diff --git a/examples/src/pages/tests/table/props/data/mutations-new-data.page.tsx b/examples/src/pages/tests/table/props/data/mutations-new-data.page.tsx new file mode 100644 index 00000000..d724e5cc --- /dev/null +++ b/examples/src/pages/tests/table/props/data/mutations-new-data.page.tsx @@ -0,0 +1,205 @@ +import * as React from 'react'; + +import { + DataSourceApi, + InfiniteTable, + InfiniteTablePropColumns, +} from '@infinite-table/infinite-react'; +import { DataSource } from '@infinite-table/infinite-react'; + +type Developer = { + id: number; + + firstName: string; + lastName: string; + + currency: string; + preferredLanguage: string; + stack: string; + canDesign: 'yes' | 'no'; + + age: number; + count?: number; +}; + +let c = 0; +function count() { + return c; +} +const getData: () => Developer[] = () => { + const data: Developer[] = [ + { + id: 1, + firstName: 'John', + lastName: 'Bob', + age: 20, + canDesign: 'yes', + currency: 'USD', + preferredLanguage: 'JavaScript', + stack: 'frontend', + count: count(), + }, + { + id: 2, + firstName: 'Marry', + lastName: 'Bob', + age: 25, + canDesign: 'yes', + currency: 'USD', + preferredLanguage: 'JavaScript', + stack: 'frontend', + count: count(), + }, + { + id: 3, + firstName: 'Bill', + lastName: 'Bobson', + age: 30, + canDesign: 'no', + currency: 'CAD', + preferredLanguage: 'TypeScript', + stack: 'frontend', + count: count(), + }, + { + id: 4, + firstName: 'Mark', + lastName: 'Twain', + age: 31, + canDesign: 'yes', + currency: 'CAD', + preferredLanguage: 'Rust', + stack: 'backend', + count: count(), + }, + { + id: 5, + firstName: 'Matthew', + lastName: 'Hilson', + age: 29, + canDesign: 'yes', + currency: 'CAD', + preferredLanguage: 'Go', + stack: 'backend', + count: count(), + }, + ]; + + return data; +}; + +const columns: InfiniteTablePropColumns = { + id: { + field: 'id', + }, + firstName: { + field: 'firstName', + }, + age: { + field: 'age', + type: 'number', + defaultEditable: true, + getValueToPersist: ({ value }) => { + return !isNaN(Number(value)) ? Number(value) : value; + }, + }, + + stack: { field: 'stack', renderMenuIcon: false }, + currency: { field: 'currency' }, + count: { field: 'count', minWidth: 150 }, +}; + +const mark: Developer = { + id: 6, + firstName: 'Mark', + lastName: 'Berg', + age: 39, + canDesign: 'no', + currency: 'USD', + preferredLanguage: 'Go', + stack: 'frontend', + count: count(), +}; + +const beforeMark: Developer = { + id: 7, + firstName: 'Before Mark', + lastName: 'Before', + age: 39, + canDesign: 'no', + currency: 'USD', + preferredLanguage: 'Go', + stack: 'frontend', + count: count(), +}; + +(globalThis as any).mutations = undefined; +(globalThis as any).onDataMutationsCalls = 0; + +export default () => { + const [data, setData] = React.useState(() => getData()); + const [dataSourceApi, setDataSourceApi] = + React.useState>(); + return ( + <> + + + + + + + + data={data} + primaryKey="id" + onReady={setDataSourceApi} + onDataMutations={({ mutations }) => { + (globalThis as any).mutations = mutations; + (globalThis as any).onDataMutationsCalls++; + }} + > + + domProps={{ + style: { + height: '100%', + }, + }} + columnSizing={{ + id: { + width: 500, + }, + }} + columnDefaultWidth={100} + columnMinWidth={50} + columns={columns} + /> + + + + ); +}; diff --git a/examples/src/pages/tests/table/props/data/mutations-new-data.spec.ts b/examples/src/pages/tests/table/props/data/mutations-new-data.spec.ts new file mode 100644 index 00000000..bed51a83 --- /dev/null +++ b/examples/src/pages/tests/table/props/data/mutations-new-data.spec.ts @@ -0,0 +1,56 @@ +import { test, expect } from '@testing'; + +export default test.describe.parallel('Mutations simple test', () => { + test('editing triggers data mutations - refreshing the data does not trigger onDataMutation call', async ({ + page, + editModel, + }) => { + await page.waitForInfinite(); + + expect( + await page.evaluate(() => (window as any).onDataMutationsCalls), + ).toBe(0); + + const cell = { + colId: 'age', + rowIndex: 1, + }; + await editModel.startEdit({ ...cell, event: 'enter', value: '120' }); + + await editModel.confirmEdit(cell); + + await page.waitForTimeout(50); + + expect( + await page.evaluate(() => (window as any).onDataMutationsCalls), + ).toBe(1); + + expect(await page.evaluate(() => (window as any).mutations.get(2))).toEqual( + [ + { + primaryKey: 2, + type: 'update', + data: { id: 2, age: 120 }, + originalData: { + id: 2, + firstName: 'Marry', + lastName: 'Bob', + age: 25, + canDesign: 'yes', + currency: 'USD', + preferredLanguage: 'JavaScript', + stack: 'frontend', + count: 0, + }, + }, + ], + ); + + await page.getByText('Refresh data').click(); + await page.waitForTimeout(50); + + expect( + await page.evaluate(() => (window as any).onDataMutationsCalls), + ).toBe(1); + }); +}); diff --git a/examples/src/pages/tests/table/props/data/onDataArrayChange.page.tsx b/examples/src/pages/tests/table/props/data/onDataArrayChange.page.tsx new file mode 100644 index 00000000..624e066e --- /dev/null +++ b/examples/src/pages/tests/table/props/data/onDataArrayChange.page.tsx @@ -0,0 +1,212 @@ +import * as React from 'react'; + +import { + DataSourceApi, + InfiniteTable, + InfiniteTablePropColumns, +} from '@infinite-table/infinite-react'; +import { DataSource } from '@infinite-table/infinite-react'; + +type Developer = { + id: number; + + firstName: string; + lastName: string; + + currency: string; + preferredLanguage: string; + stack: string; + canDesign: 'yes' | 'no'; + + age: number; + count?: number; +}; + +let c = 0; +function count() { + return c; +} +const getData: () => Developer[] = () => { + const data: Developer[] = [ + { + id: 1, + firstName: 'John', + lastName: 'Bob', + age: 20, + canDesign: 'yes', + currency: 'USD', + preferredLanguage: 'JavaScript', + stack: 'frontend', + count: count(), + }, + { + id: 2, + firstName: 'Marry', + lastName: 'Bob', + age: 25, + canDesign: 'yes', + currency: 'USD', + preferredLanguage: 'JavaScript', + stack: 'frontend', + count: count(), + }, + { + id: 3, + firstName: 'Bill', + lastName: 'Bobson', + age: 30, + canDesign: 'no', + currency: 'CAD', + preferredLanguage: 'TypeScript', + stack: 'frontend', + count: count(), + }, + { + id: 4, + firstName: 'Mark', + lastName: 'Twain', + age: 31, + canDesign: 'yes', + currency: 'CAD', + preferredLanguage: 'Rust', + stack: 'backend', + count: count(), + }, + { + id: 5, + firstName: 'Matthew', + lastName: 'Hilson', + age: 29, + canDesign: 'yes', + currency: 'CAD', + preferredLanguage: 'Go', + stack: 'backend', + count: count(), + }, + ]; + + return data; +}; + +const columns: InfiniteTablePropColumns = { + id: { + field: 'id', + }, + firstName: { + field: 'firstName', + }, + age: { + field: 'age', + type: 'number', + defaultEditable: true, + getValueToPersist: ({ value }) => { + return !isNaN(Number(value)) ? Number(value) : value; + }, + }, + + stack: { field: 'stack', renderMenuIcon: false }, + currency: { field: 'currency' }, + count: { field: 'count', minWidth: 150 }, +}; + +const mark: Developer = { + id: 6, + firstName: 'Mark', + lastName: 'Berg', + age: 39, + canDesign: 'no', + currency: 'USD', + preferredLanguage: 'Go', + stack: 'frontend', + count: count(), +}; + +const beforeMark: Developer = { + id: 7, + firstName: 'Before Mark', + lastName: 'Before', + age: 39, + canDesign: 'no', + currency: 'USD', + preferredLanguage: 'Go', + stack: 'frontend', + count: count(), +}; + +(globalThis as any).mutations = undefined; +(globalThis as any).onDataMutationsCalls = 0; + +export default () => { + const [data, setData] = React.useState(() => getData()); + const [dataSourceApi, setDataSourceApi] = + React.useState>(); + return ( + <> + + + + + + + + data={data} + primaryKey="id" + onReady={setDataSourceApi} + onDataArrayChange={(dataArray, info) => { + console.log({ + dataArray, + info, + }); + }} + onDataMutations={({ mutations }) => { + console.log(mutations); + (globalThis as any).mutations = mutations; + (globalThis as any).onDataMutationsCalls++; + }} + > + + domProps={{ + style: { + height: '100%', + }, + }} + columnSizing={{ + id: { + width: 500, + }, + }} + columnDefaultWidth={100} + columnMinWidth={50} + columns={columns} + /> + + + + ); +}; diff --git a/source/src/components/DataSource/Indexer.ts b/source/src/components/DataSource/Indexer.ts index 183cacb0..12adcb39 100644 --- a/source/src/components/DataSource/Indexer.ts +++ b/source/src/components/DataSource/Indexer.ts @@ -24,7 +24,7 @@ export class Indexer { ) => { const { cache, toPrimaryKey } = options; - if (cache) { + if (cache && !cache.isEmpty()) { // because of React.StrictMode, we need to clone the array and return a copy // not very efficient for large arrays // TODO IMPORTANT seek to improve this diff --git a/source/src/components/DataSource/index.tsx b/source/src/components/DataSource/index.tsx index 38776cb5..fe870f74 100644 --- a/source/src/components/DataSource/index.tsx +++ b/source/src/components/DataSource/index.tsx @@ -105,7 +105,8 @@ function DataSourceCmp({ children }: { children: DataSourceChildren }) { if ( componentState.onDataMutations && - componentState.originalDataArrayChangedInfo.mutations + componentState.originalDataArrayChangedInfo.mutations && + componentState.originalDataArrayChangedInfo.mutations.size ) { componentState.onDataMutations({ primaryKeyField: diff --git a/source/src/components/DataSource/state/reducer.ts b/source/src/components/DataSource/state/reducer.ts index 1fd23992..1ba7fe4a 100644 --- a/source/src/components/DataSource/state/reducer.ts +++ b/source/src/components/DataSource/state/reducer.ts @@ -263,7 +263,7 @@ export function concludeReducer(params: { } if (cache) { cache.clear(); - state.cache = cache; + state.cache = undefined; } const { filterFunction, filterValue, filterTypes, operatorsByFilterType } = @@ -639,7 +639,7 @@ export function concludeReducer(params: { if (originalDataArrayChanged) { state.originalDataArrayChangedInfo = { timestamp: now, - mutations, + mutations: mutations?.size ? mutations : undefined, }; } diff --git a/www/content/docs/releases/index.page.md b/www/content/docs/releases/index.page.md index f76a115e..1050d45a 100644 --- a/www/content/docs/releases/index.page.md +++ b/www/content/docs/releases/index.page.md @@ -3,6 +3,10 @@ title: Releases description: All releases | Infinite Table DataGrid for React --- +## 3.0.4 + +@milestone id="99" + ## 3.0.3 @milestone id="98"