Skip to content

Commit a86a48e

Browse files
committed
refactor: KeyValueTable - move methods to utils and code optimization for better state management
1 parent a63823e commit a86a48e

File tree

3 files changed

+186
-175
lines changed

3 files changed

+186
-175
lines changed

src/Shared/Components/KeyValueTable/KeyValueTable.component.tsx

Lines changed: 35 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,26 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { useCallback, useMemo, useState } from 'react'
17+
import { useEffect, useMemo, useState } from 'react'
1818

19-
import { SortingOrder } from '@Common/Constants'
20-
import { debounce, noop } from '@Common/Helper'
2119
import { useStateFilters } from '@Common/Hooks'
22-
import { DEFAULT_SECRET_PLACEHOLDER } from '@Shared/constants'
23-
import { stringComparatorBySortOrder } from '@Shared/Helpers'
2420

25-
import { DynamicDataTable, DynamicDataTableCellValidationState } from '../DynamicDataTable'
26-
import { DUPLICATE_KEYS_VALIDATION_MESSAGE, EMPTY_KEY_VALIDATION_MESSAGE } from './constants'
27-
import {
28-
KeyValueTableData,
29-
KeyValueTableDataType,
30-
KeyValueTableInternalProps,
31-
KeyValueTableProps,
32-
} from './KeyValueTable.types'
21+
import { DynamicDataTable } from '../DynamicDataTable'
22+
import { KeyValueTableDataType, KeyValueTableInternalProps, KeyValueTableProps } from './KeyValueTable.types'
3323
import {
3424
getEmptyRow,
3525
getKeyValueHeaders,
36-
getKeyValueInitialCellError,
37-
getKeyValueInitialRows,
38-
getKeyValueTableKeysFrequency,
26+
getKeyValueTableCellError,
27+
getKeyValueTableRows,
28+
getKeyValueTableSortedRows,
3929
getModifiedDataForOnChange,
4030
} from './utils'
4131

4232
import './KeyValueTable.scss'
4333

4434
export const KeyValueTable = ({
4535
headerLabel,
46-
initialRows,
36+
rows: initialRows,
4737
placeholder,
4838
maskValue,
4939
isSortable,
@@ -53,133 +43,50 @@ export const KeyValueTable = ({
5343
readOnly,
5444
showError,
5545
validationSchema: parentValidationSchema,
56-
errorMessages: parentErrorMessages = [],
5746
onError,
5847
validateDuplicateKeys = false,
5948
validateEmptyKeys = false,
6049
}: KeyValueTableProps) => {
6150
// STATES
62-
const [rows, setRows] = useState<KeyValueTableInternalProps['rows']>(
63-
getKeyValueInitialRows({ initialRows, placeholder }),
64-
)
65-
66-
const [cellError, setCellError] = useState<KeyValueTableInternalProps['cellError']>(
67-
getKeyValueInitialCellError(rows),
68-
)
51+
const [cellError, setCellError] = useState<KeyValueTableInternalProps['cellError']>({})
6952

7053
// HOOKS
7154
const { sortBy, sortOrder, handleSorting } = useStateFilters<KeyValueTableDataType>({
7255
initialSortKey: isSortable ? 'key' : null,
7356
})
7457

75-
const rowWithMaskedValues = useMemo<typeof rows>(() => {
76-
if (maskValue && Object.keys(maskValue).length) {
77-
return rows.map((row) => ({
78-
...row,
79-
data: {
80-
...row.data,
81-
key: {
82-
...row.data.key,
83-
value: maskValue.key ? DEFAULT_SECRET_PLACEHOLDER : row.data.key.value,
84-
},
85-
value: {
86-
...row.data.value,
87-
value: maskValue.value ? DEFAULT_SECRET_PLACEHOLDER : row.data.value.value,
88-
},
89-
},
90-
}))
91-
}
92-
93-
return rows
94-
}, [rows, maskValue])
95-
96-
const debounceOnChange = useCallback(
97-
debounce((modifiedRows: KeyValueTableData[]) =>
98-
typeof onChange === 'function' ? onChange(modifiedRows) : noop,
99-
),
100-
[],
58+
// COMPUTED ROWS FOR DYNAMIC DATA TABLE
59+
const rows = useMemo<KeyValueTableInternalProps['rows']>(
60+
() => getKeyValueTableRows({ rows: initialRows, placeholder, maskValue }),
61+
[initialRows, placeholder, maskValue, isSortable, sortOrder, sortBy],
10162
)
10263

103-
// METHODS
104-
const validationSchema = (
105-
value: Parameters<typeof parentValidationSchema>[0],
106-
key: Parameters<typeof parentValidationSchema>[1],
107-
rowId: Parameters<typeof parentValidationSchema>[2],
108-
keysFrequency: Record<string, number> = {},
109-
): DynamicDataTableCellValidationState => {
110-
const trimmedValue = value.trim()
111-
112-
if (validateDuplicateKeys && key === 'key' && (keysFrequency[trimmedValue] ?? 0) > 1) {
113-
return {
114-
isValid: false,
115-
errorMessages: [DUPLICATE_KEYS_VALIDATION_MESSAGE],
116-
}
117-
}
118-
119-
if (validateEmptyKeys && key === 'key' && !trimmedValue) {
120-
const isValuePresentAtRow = rows.some(({ id, data }) => id === rowId && data.value.value.trim())
121-
if (isValuePresentAtRow) {
122-
return {
123-
isValid: false,
124-
errorMessages: [EMPTY_KEY_VALIDATION_MESSAGE],
125-
}
126-
}
127-
}
128-
129-
if (parentValidationSchema) {
130-
const isValid = parentValidationSchema(value, key, rowId)
131-
return {
132-
isValid,
133-
errorMessages: !isValid ? parentErrorMessages : [],
134-
}
135-
}
136-
137-
return {
138-
isValid: true,
139-
errorMessages: [],
140-
}
141-
}
64+
// Set cell error on mount
65+
useEffect(() => {
66+
const { isValid, updatedCellError } = getKeyValueTableCellError({
67+
rows,
68+
validateDuplicateKeys,
69+
validateEmptyKeys,
70+
validationSchema: parentValidationSchema,
71+
})
14272

143-
const checkAllRowsAreValid = (updatedRows: typeof rows) => {
144-
let isValid = true
145-
146-
const updatedCellError = updatedRows.reduce((acc, { data, id }) => {
147-
const keyError = validationSchema(
148-
data.key.value,
149-
'key',
150-
id,
151-
validateDuplicateKeys ? getKeyValueTableKeysFrequency(rows) : {},
152-
)
153-
const valueError = validationSchema(data.value.value, 'value', id)
154-
155-
if (isValid && !(keyError.isValid && valueError.isValid)) {
156-
isValid = false
157-
}
158-
159-
acc[id] = {
160-
key: keyError,
161-
value: valueError,
162-
}
163-
164-
return acc
165-
}, {})
166-
167-
return { isValid, updatedCellError }
168-
}
73+
setCellError(updatedCellError)
74+
onError?.(!isValid)
75+
}, [])
16976

170-
const setUpdatedRows = (updatedRows: typeof rows, shouldDebounceChange = false) => {
171-
const { isValid, updatedCellError } = checkAllRowsAreValid(updatedRows)
77+
// METHODS
78+
const setUpdatedRows = (updatedRows: typeof rows) => {
79+
const { isValid, updatedCellError } = getKeyValueTableCellError({
80+
rows: updatedRows,
81+
validateDuplicateKeys,
82+
validateEmptyKeys,
83+
validationSchema: parentValidationSchema,
84+
})
17285

173-
setRows(updatedRows)
17486
setCellError(updatedCellError)
175-
17687
onError?.(!isValid)
17788

178-
if (shouldDebounceChange) {
179-
debounceOnChange(getModifiedDataForOnChange(updatedRows))
180-
} else {
181-
onChange?.(getModifiedDataForOnChange(updatedRows))
182-
}
89+
onChange(getModifiedDataForOnChange(updatedRows))
18390
}
18491

18592
const onRowAdd = () => {
@@ -211,31 +118,13 @@ export const KeyValueTable = ({
211118
updatedRows[rowIndex] = selectedRow
212119
}
213120

214-
setUpdatedRows(updatedRows, true)
215-
}
216-
217-
const onSorting = (_sortBy: KeyValueTableDataType) => {
218-
handleSorting(_sortBy)
219-
220-
if (isSortable) {
221-
setRows((prevRows) => {
222-
const sortedRows = prevRows
223-
sortedRows.sort((a, b) =>
224-
stringComparatorBySortOrder(
225-
a.data[_sortBy].value,
226-
b.data[_sortBy].value,
227-
sortOrder === SortingOrder.ASC ? SortingOrder.DESC : SortingOrder.ASC,
228-
),
229-
)
230-
return sortedRows
231-
})
232-
}
121+
setUpdatedRows(updatedRows)
233122
}
234123

235124
return (
236125
<DynamicDataTable
237126
headers={getKeyValueHeaders({ headerLabel, isSortable })}
238-
rows={rowWithMaskedValues}
127+
rows={getKeyValueTableSortedRows({ isSortable, rows, sortBy, sortOrder })}
239128
cellError={showError ? cellError : {}}
240129
onRowAdd={onRowAdd}
241130
onRowDelete={onRowDelete}
@@ -246,7 +135,7 @@ export const KeyValueTable = ({
246135
sortingConfig={{
247136
sortBy,
248137
sortOrder,
249-
handleSorting: onSorting,
138+
handleSorting,
250139
}}
251140
/>
252141
)

src/Shared/Components/KeyValueTable/KeyValueTable.types.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ export type KeyValueTableProps = Pick<
9292
*/
9393
headerLabel: KeyValueHeaderLabel
9494
/**
95-
* The initial rows of the key-value table.
95+
* The rows of the key-value table.
9696
*/
97-
initialRows: KeyValueTableRowType[]
97+
rows: KeyValueTableRowType[]
9898
/**
9999
* An optional configuration to mask values in the table.
100100
*/
@@ -112,22 +112,29 @@ export type KeyValueTableProps = Pick<
112112
*
113113
* @param data - The updated table data.
114114
*/
115-
onChange?: (data: KeyValueTableData[]) => void
115+
onChange: (data: KeyValueTableData[]) => void
116116
/**
117117
* A function to validate the value of a cell.
118118
*
119119
* @param value - The value to validate.
120120
* @param key - The key of the header associated with the value.
121-
* @param rowId - The id of the row containing the value.
121+
* @param row - The row containing the value.
122122
* @returns A boolean indicating whether the value is valid. If false,
123123
* `showError` should be set to `true` and `errorMessages` should
124124
* provide an array of error messages to display.
125125
*/
126-
validationSchema?: (value: string, key: KeyValueTableDataType, rowId: KeyValueTableRowType['id']) => boolean
127-
/**
128-
* An array of error messages to display in the cell error tooltip.
129-
*/
130-
errorMessages?: string[]
126+
validationSchema?: (
127+
value: string,
128+
key: KeyValueTableDataType,
129+
row: KeyValueTableRowType,
130+
) => {
131+
/** Boolean indicating if the cell data is valid or not. */
132+
isValid: boolean
133+
/**
134+
* An array of error messages to display in the cell error tooltip.
135+
*/
136+
errorMessages?: string[]
137+
}
131138
/**
132139
* A callback function triggered when an error occurs in the table.
133140
*
@@ -136,3 +143,11 @@ export type KeyValueTableProps = Pick<
136143
*/
137144
onError?: (errorState: boolean) => void
138145
} & ErrorUIProps
146+
147+
export type KeyValueValidationSchemaProps = {
148+
value: Parameters<KeyValueTableProps['validationSchema']>[0]
149+
key: Parameters<KeyValueTableProps['validationSchema']>[1]
150+
row: Parameters<KeyValueTableProps['validationSchema']>[2]
151+
keysFrequency?: Record<string, number>
152+
} & Pick<KeyValueTableProps, 'validateDuplicateKeys' | 'validateEmptyKeys' | 'validationSchema'> &
153+
Partial<Pick<KeyValueTableInternalProps, 'rows'>>

0 commit comments

Comments
 (0)