Skip to content

Commit fa4b50f

Browse files
authored
Merge pull request #86 from UgnisSoftware/UGN-318
Fix UGN-318 fix indexes messing up after delete
2 parents cff6f4d + f4f6f90 commit fa4b50f

File tree

5 files changed

+98
-28
lines changed

5 files changed

+98
-28
lines changed

src/steps/ValidationStep/ValidationStep.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, { useCallback, useMemo, useState } from "react"
2-
import { Box, Button, Heading, ModalBody, Switch, Text, useStyleConfig } from "@chakra-ui/react"
2+
import { Box, Button, Heading, ModalBody, Switch, useStyleConfig } from "@chakra-ui/react"
33
import { ContinueButton } from "../../components/ContinueButton"
44
import { useRsi } from "../../hooks/useRsi"
55
import type { Meta } from "./types"
6-
import { addErrorsAndRunHooks, addIndexes } from "./utils/dataMutations"
6+
import { addErrorsAndRunHooks } from "./utils/dataMutations"
77
import { generateColumns } from "./components/columns"
88
import { Table } from "../../components/Table"
99
import { SubmitDataAlert } from "../../components/Alerts/SubmitDataAlert"
@@ -20,7 +20,7 @@ export const ValidationStep = <T extends string>({ initialData }: Props<T>) => {
2020
const styles = useStyleConfig("ValidationStep") as typeof themeOverrides["components"]["ValidationStep"]["baseStyle"]
2121

2222
const [data, setData] = useState<(Data<T> & Meta)[]>(
23-
useMemo(() => addErrorsAndRunHooks<T>(addIndexes<T>(initialHook(initialData)), fields, rowHook, tableHook), []),
23+
useMemo(() => addErrorsAndRunHooks<T>(initialHook(initialData), fields, rowHook, tableHook), []),
2424
)
2525
const [selectedRows, setSelectedRows] = useState<ReadonlySet<number | string>>(new Set())
2626
const [filterByErrors, setFilterByErrors] = useState(false)
@@ -30,7 +30,7 @@ export const ValidationStep = <T extends string>({ initialData }: Props<T>) => {
3030
(rows: typeof data) => {
3131
setData(addErrorsAndRunHooks<T>(rows, fields, rowHook, tableHook))
3232
},
33-
[setData, addErrorsAndRunHooks, rowHook, tableHook],
33+
[setData, addErrorsAndRunHooks, rowHook, tableHook, fields],
3434
)
3535

3636
const deleteSelectedRows = () => {
@@ -41,24 +41,25 @@ export const ValidationStep = <T extends string>({ initialData }: Props<T>) => {
4141
}
4242
}
4343

44-
const updateRowTable = useCallback(
44+
const updateRow = useCallback(
4545
(rows: typeof data, changedData?: RowsChangeData<typeof data[number]>) => {
46-
const changes = changedData?.indexes.reduce((acc, val) => {
47-
const realIndex = rows[val].__index
48-
acc[realIndex] = rows[val]
46+
const changes = changedData?.indexes.reduce((acc, index) => {
47+
// when data is filtered val !== actual index in data
48+
const realIndex = data.findIndex((value) => value.__index === rows[index].__index)
49+
acc[realIndex] = rows[index]
4950
return acc
5051
}, {} as Record<number, typeof data[number]>)
5152
const newData = Object.assign([], data, changes)
5253
updateData(newData)
5354
},
54-
[data, setData, addErrorsAndRunHooks, rowHook, tableHook],
55+
[data, updateData],
5556
)
5657

5758
const columns = useMemo(() => generateColumns(fields), [fields, generateColumns])
5859

5960
const tableData = useMemo(() => {
6061
if (filterByErrors) {
61-
return data.filter((value, index) => {
62+
return data.filter((value) => {
6263
if (value?.__errors) {
6364
return Object.values(value.__errors)?.filter((err) => err.level === "error").length
6465
}
@@ -127,7 +128,7 @@ export const ValidationStep = <T extends string>({ initialData }: Props<T>) => {
127128
<Table
128129
rowKeyGetter={rowKeyGetter}
129130
rows={tableData}
130-
onRowsChange={updateRowTable}
131+
onRowsChange={updateRow}
131132
columns={columns}
132133
selectedRows={selectedRows}
133134
onSelectedRowsChange={setSelectedRows}

src/steps/ValidationStep/tests/ValidationStep.test.tsx

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ describe("Validation step tests", () => {
154154

155155
// don't really know another way to select an empty cell
156156
const emptyCell = screen.getAllByRole("gridcell", { name: undefined })[1]
157-
console.log(emptyCell)
158157
userEvent.click(emptyCell)
159158

160159
await userEvent.keyboard(FINAL_NAME + "{enter}")
@@ -328,6 +327,72 @@ describe("Validation step tests", () => {
328327
expect(validRow).toBeInTheDocument()
329328
})
330329

330+
test("Deletes selected rows, changes the last one", async () => {
331+
const FIRST_DELETE = "first"
332+
const SECOND_DELETE = "second"
333+
const THIRD = "third"
334+
const THIRD_CHANGED = "third_changed"
335+
336+
const initialData = [
337+
{
338+
name: FIRST_DELETE,
339+
},
340+
{
341+
name: SECOND_DELETE,
342+
},
343+
{
344+
name: THIRD,
345+
},
346+
]
347+
const fields = [
348+
{
349+
label: "Name",
350+
key: "name",
351+
fieldType: {
352+
type: "input",
353+
},
354+
},
355+
] as const
356+
render(
357+
<Providers theme={defaultTheme} rsiValues={{ ...mockValues, fields }}>
358+
<ModalWrapper isOpen={true} onClose={() => {}}>
359+
<ValidationStep initialData={initialData} />
360+
</ModalWrapper>
361+
</Providers>,
362+
)
363+
364+
const allRowsWithHeader = await screen.findAllByRole("row")
365+
expect(allRowsWithHeader).toHaveLength(4)
366+
367+
const switchFilters = screen.getAllByRole("checkbox", {
368+
name: "Select",
369+
})
370+
371+
userEvent.click(switchFilters[0])
372+
userEvent.click(switchFilters[1])
373+
374+
const discardButton = screen.getByRole("button", {
375+
name: "Discard selected rows",
376+
})
377+
378+
userEvent.click(discardButton)
379+
380+
const filteredRowsWithHeader = await screen.findAllByRole("row")
381+
expect(filteredRowsWithHeader).toHaveLength(2)
382+
383+
const nameCell = screen.getByRole("gridcell", {
384+
name: THIRD,
385+
})
386+
387+
userEvent.click(nameCell)
388+
389+
screen.getByRole<HTMLInputElement>("textbox")
390+
await userEvent.keyboard(THIRD_CHANGED + "{enter}")
391+
392+
const validRow = screen.getByText(THIRD_CHANGED)
393+
expect(validRow).toBeInTheDocument()
394+
})
395+
331396
test("All inputs change values", async () => {
332397
const NAME = "John"
333398
const NEW_NAME = "Johnny"

src/steps/ValidationStep/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Info } from "../../types"
22

3-
export type Meta = { __index: number; __errors?: Error | null }
3+
export type Meta = { __index: string; __errors?: Error | null }
44
export type Error = { [key: string]: Info }
5-
export type Errors = { [id: string]: Error }
5+
export type Errors = { [id: string]: Error }

src/steps/ValidationStep/utils/dataMutations.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Data, Fields, Info, RowHook, TableHook } from "../../../types"
22
import type { Meta, Errors } from "../types"
33

44
export const addErrorsAndRunHooks = <T extends string>(
5-
data: (Data<T> & Meta)[],
5+
data: (Data<T> & Partial<Meta>)[],
66
fields: Fields<T>,
77
rowHook?: RowHook<T>,
88
tableHook?: TableHook<T>,
@@ -17,11 +17,11 @@ export const addErrorsAndRunHooks = <T extends string>(
1717
}
1818

1919
if (tableHook) {
20-
data = addIndexes(tableHook(data, addHookError))
20+
data = tableHook(data, addHookError)
2121
}
2222

2323
if (rowHook) {
24-
data = addIndexes(data.map((value, index) => rowHook(value, (...props) => addHookError(index, ...props), data)))
24+
data = data.map((value, index) => rowHook(value, (...props) => addHookError(index, ...props), data))
2525
}
2626

2727
fields.forEach((field) => {
@@ -78,20 +78,18 @@ export const addErrorsAndRunHooks = <T extends string>(
7878
})
7979

8080
return data.map((value, index) => {
81+
// This is required only for table. Mutates to prevent needless rerenders
82+
if (!("__index" in value)) {
83+
value.__index = crypto.randomUUID()
84+
}
85+
const newValue = value as Data<T> & Meta
86+
8187
if (errors[index]) {
82-
return { ...value, __errors: errors[index] }
88+
return { ...newValue, __errors: errors[index] }
8389
}
8490
if (!errors[index] && value?.__errors) {
85-
return { ...value, __errors: null }
91+
return { ...newValue, __errors: null }
8692
}
87-
return value
93+
return newValue
8894
})
8995
}
90-
91-
export const addIndexes = <T extends string>(arr: Data<T>[]): (Data<T> & { __index: number })[] =>
92-
arr.map((value, index) => {
93-
if ("__index" in value) {
94-
return value as Data<T> & { __index: number }
95-
}
96-
return { ...value, __index: index }
97-
})

src/tests/setup.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
const nodeCrypto = require("crypto")
2+
13
// Yeeted from https://github.com/adazzle/react-data-grid/blob/main/test/setup.ts
24
if (typeof window !== "undefined") {
5+
if (!window.crypto) {
6+
;(window as any).crypto = nodeCrypto
7+
}
8+
39
window.ResizeObserver ??= class {
410
callback: ResizeObserverCallback
511

0 commit comments

Comments
 (0)