0
# Editing Capabilities
1
2
Inline editing components for text and select inputs with validation support and row-level editing controls.
3
4
## Capabilities
5
6
### EditableTextCell
7
8
Inline editable text cell component with validation support.
9
10
```typescript { .api }
11
/**
12
* Inline editable text cell component
13
* @param props - EditableTextCell configuration props
14
* @returns EditableTextCell component
15
*/
16
function EditableTextCell(props: IEditableTextCell): React.FunctionComponent<IEditableTextCell>;
17
18
interface IEditableTextCell extends React.HTMLProps<HTMLDivElement> {
19
/** The current value of the text input */
20
value: string;
21
/** Row index of this text cell */
22
rowIndex: number;
23
/** Cell index of this text cell */
24
cellIndex: number;
25
/** Props to build the input */
26
props: EditableTextCellProps;
27
/** Event handler which fires when user changes the text in this cell */
28
handleTextInputChange: (
29
newValue: string,
30
event: React.FormEvent<HTMLInputElement>,
31
rowIndex: number,
32
cellIndex: number
33
) => void;
34
/** Accessible label of the text input */
35
inputAriaLabel: string;
36
/** Flag indicating if the text input is disabled */
37
isDisabled?: boolean;
38
}
39
40
interface EditableTextCellProps {
41
/** Name of the input */
42
name: string;
43
/** Value to display in the cell */
44
value: string;
45
/** Editable value (can differ from display value) */
46
editableValue?: string;
47
/** Whether the cell is valid */
48
isValid?: boolean;
49
/** Error text to display */
50
errorText?: string;
51
/** Arbitrary data to pass to the internal text input */
52
[key: string]: any;
53
}
54
```
55
56
**Usage Examples:**
57
58
```typescript
59
import { EditableTextCell } from "@patternfly/react-table";
60
61
// Basic editable text cell
62
<Td>
63
<EditableTextCell
64
value={rowData.name}
65
rowIndex={rowIndex}
66
cellIndex={0}
67
props={{
68
name: 'name',
69
value: rowData.name,
70
editableValue: editValues[`${rowIndex}-0`],
71
isValid: validationResults[`${rowIndex}-0`]?.isValid !== false
72
}}
73
handleTextInputChange={(newValue, event, rowIndex, cellIndex) => {
74
setEditValues(prev => ({
75
...prev,
76
[`${rowIndex}-${cellIndex}`]: newValue
77
}));
78
validateCell(rowIndex, cellIndex, newValue);
79
}}
80
inputAriaLabel={`Edit name for row ${rowIndex}`}
81
/>
82
</Td>
83
84
// Editable text cell with validation error
85
<Td>
86
<EditableTextCell
87
value={rowData.email}
88
rowIndex={rowIndex}
89
cellIndex={1}
90
props={{
91
name: 'email',
92
value: rowData.email,
93
editableValue: editValues[`${rowIndex}-1`],
94
isValid: false,
95
errorText: 'Please enter a valid email address',
96
}}
97
handleTextInputChange={handleEmailChange}
98
inputAriaLabel={`Edit email for row ${rowIndex}`}
99
isDisabled={!isEditing}
100
/>
101
</Td>
102
```
103
104
### EditableSelectInputCell
105
106
Inline editable select cell component with support for single and multi-select.
107
108
```typescript { .api }
109
/**
110
* Inline editable select cell component
111
* @param props - EditableSelectInputCell configuration props
112
* @returns EditableSelectInputCell component
113
*/
114
function EditableSelectInputCell(props: IEditableSelectInputCell): React.FunctionComponent<IEditableSelectInputCell>;
115
116
interface IEditableSelectInputCell extends Omit<React.HTMLProps<HTMLElement | HTMLDivElement>, 'onSelect' | 'onToggle'> {
117
/** Row index of this select input cell */
118
rowIndex: number;
119
/** Cell index of this select input cell */
120
cellIndex: number;
121
/** Props to build the select component */
122
props: EditableSelectInputProps;
123
/** Event handler which fires when user selects an option in this cell */
124
onSelect: (
125
event: React.MouseEvent | React.ChangeEvent,
126
newValue: any | any[],
127
rowIndex: number,
128
cellIndex: number,
129
isPlaceholder?: boolean
130
) => void;
131
/** Options to display in the expandable select menu */
132
options?: React.ReactElement<any>[];
133
/** Flag indicating the select input is disabled */
134
isDisabled?: boolean;
135
/** Flag indicating the toggle gets placeholder styles */
136
isPlaceholder?: boolean;
137
/** Current selected options to display as the read only value of the table cell */
138
selections?: any | any[];
139
/** Flag indicating the select menu is open */
140
isOpen?: boolean;
141
/** Event handler which fires when the select toggle is toggled */
142
onToggle?: (event: React.MouseEvent | undefined) => void;
143
/** Event handler which fires when the user clears the selections */
144
clearSelection?: (event: React.MouseEvent, rowIndex: number, cellIndex: number) => void;
145
}
146
147
interface EditableSelectInputProps {
148
/** Name of the select input */
149
name: string;
150
/** Value to display in the cell */
151
value: string | string[];
152
/** Flag controlling isOpen state of select */
153
isSelectOpen: boolean;
154
/** Single select option value for single select menus, or array for multi select */
155
selected: any | any[];
156
/** Array of react elements to display in the select menu */
157
options: React.ReactElement<any>[];
158
/** Props to be passed down to the select component */
159
editableSelectProps?: SelectProps;
160
/** Error text to display */
161
errorText?: string;
162
/** Arbitrary data to pass to the internal select component */
163
[key: string]: any;
164
}
165
```
166
167
**Usage Examples:**
168
169
```typescript
170
import { EditableSelectInputCell, SelectOption } from "@patternfly/react-table";
171
172
// Basic editable select cell
173
const statusOptions = [
174
<SelectOption key="active" value="active">Active</SelectOption>,
175
<SelectOption key="inactive" value="inactive">Inactive</SelectOption>,
176
<SelectOption key="pending" value="pending">Pending</SelectOption>
177
];
178
179
<Td>
180
<EditableSelectInputCell
181
rowIndex={rowIndex}
182
cellIndex={2}
183
props={{
184
name: 'status',
185
value: rowData.status,
186
isSelectOpen: openSelects[`${rowIndex}-2`] || false,
187
selected: editValues[`${rowIndex}-2`] || rowData.status,
188
options: statusOptions
189
}}
190
options={statusOptions}
191
selections={editValues[`${rowIndex}-2`] || rowData.status}
192
isOpen={openSelects[`${rowIndex}-2`] || false}
193
onSelect={(event, value, rowIndex, cellIndex) => {
194
setEditValues(prev => ({
195
...prev,
196
[`${rowIndex}-${cellIndex}`]: value
197
}));
198
setOpenSelects(prev => ({
199
...prev,
200
[`${rowIndex}-${cellIndex}`]: false
201
}));
202
}}
203
onToggle={(event) => {
204
setOpenSelects(prev => ({
205
...prev,
206
[`${rowIndex}-2`]: !prev[`${rowIndex}-2`]
207
}));
208
}}
209
/>
210
</Td>
211
212
// Multi-select editable cell
213
<Td>
214
<EditableSelectInputCell
215
rowIndex={rowIndex}
216
cellIndex={3}
217
props={{
218
name: 'tags',
219
value: rowData.tags,
220
isSelectOpen: openSelects[`${rowIndex}-3`] || false,
221
selected: editValues[`${rowIndex}-3`] || rowData.tags,
222
options: tagOptions,
223
editableSelectProps: { variant: 'checkbox' }
224
}}
225
options={tagOptions}
226
selections={editValues[`${rowIndex}-3`] || rowData.tags}
227
isOpen={openSelects[`${rowIndex}-3`] || false}
228
onSelect={handleMultiSelect}
229
onToggle={toggleMultiSelect}
230
clearSelection={(event, rowIndex, cellIndex) => {
231
setEditValues(prev => ({
232
...prev,
233
[`${rowIndex}-${cellIndex}`]: []
234
}));
235
}}
236
/>
237
</Td>
238
```
239
240
## Row-Level Editing
241
242
### Row Edit Configuration
243
244
```typescript { .api }
245
// Row editing event handler
246
type OnRowEdit = (
247
event: React.MouseEvent<HTMLButtonElement>,
248
type: RowEditType,
249
isEditable?: boolean,
250
rowIndex?: number,
251
validationErrors?: RowErrors
252
) => void;
253
254
// Row edit action types
255
type RowEditType = 'save' | 'cancel' | 'edit';
256
257
// Row validation errors
258
interface RowErrors {
259
[name: string]: string[];
260
}
261
262
// Row validation definition
263
interface IValidatorDef {
264
validator: (value: string) => boolean;
265
errorText: string;
266
name: string;
267
}
268
```
269
270
### Row Edit Properties
271
272
```typescript { .api }
273
// IRow interface includes editing properties
274
interface IRow extends RowType {
275
/** Whether the row is editable */
276
isEditable?: boolean;
277
/** Whether the row is valid */
278
isValid?: boolean;
279
/** Array of validation functions to run against every cell for a given row */
280
rowEditValidationRules?: IValidatorDef[];
281
/** Aria label for edit button in inline edit */
282
rowEditBtnAriaLabel?: (idx: number) => string;
283
/** Aria label for save button in inline edit */
284
rowSaveBtnAriaLabel?: (idx: number) => string;
285
/** Aria label for cancel button in inline edit */
286
rowCancelBtnAriaLabel?: (idx: number) => string;
287
}
288
```
289
290
**Usage Examples:**
291
292
```typescript
293
// Row with editing configuration
294
const editableRows = [
295
{
296
cells: ['John Doe', 'john@example.com', 'Active'],
297
isEditable: editingRows.includes(0),
298
isValid: rowValidation[0]?.isValid,
299
rowEditValidationRules: [
300
{
301
name: 'email',
302
validator: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
303
errorText: 'Please enter a valid email address'
304
},
305
{
306
name: 'name',
307
validator: (value) => value.trim().length > 0,
308
errorText: 'Name is required'
309
}
310
],
311
rowEditBtnAriaLabel: (idx) => `Edit row ${idx}`,
312
rowSaveBtnAriaLabel: (idx) => `Save changes for row ${idx}`,
313
rowCancelBtnAriaLabel: (idx) => `Cancel editing row ${idx}`
314
}
315
];
316
317
// Row edit handlers
318
const handleRowEdit = (event, type, isEditable, rowIndex, validationErrors) => {
319
switch (type) {
320
case 'edit':
321
setEditingRows(prev => [...prev, rowIndex]);
322
break;
323
case 'save':
324
if (!validationErrors || Object.keys(validationErrors).length === 0) {
325
// Save the changes
326
saveRowChanges(rowIndex);
327
setEditingRows(prev => prev.filter(idx => idx !== rowIndex));
328
}
329
break;
330
case 'cancel':
331
// Discard changes
332
discardRowChanges(rowIndex);
333
setEditingRows(prev => prev.filter(idx => idx !== rowIndex));
334
break;
335
}
336
};
337
```
338
339
## Validation Support
340
341
### Cell-Level Validation
342
343
```typescript { .api }
344
// Validation function for individual cells
345
const validateCell = (rowIndex: number, cellIndex: number, value: string): boolean => {
346
const row = rows[rowIndex];
347
if (row.rowEditValidationRules) {
348
const rule = row.rowEditValidationRules[cellIndex];
349
if (rule && !rule.validator(value)) {
350
setValidationErrors(prev => ({
351
...prev,
352
[`${rowIndex}-${cellIndex}`]: rule.errorText
353
}));
354
return false;
355
}
356
}
357
358
// Clear validation error if valid
359
setValidationErrors(prev => {
360
const newErrors = { ...prev };
361
delete newErrors[`${rowIndex}-${cellIndex}`];
362
return newErrors;
363
});
364
return true;
365
};
366
```
367
368
### Row-Level Validation
369
370
```typescript { .api }
371
// Validation function for entire rows
372
const validateRow = (rowIndex: number): RowErrors | null => {
373
const row = rows[rowIndex];
374
const errors: RowErrors = {};
375
376
if (row.rowEditValidationRules) {
377
row.rowEditValidationRules.forEach((rule, cellIndex) => {
378
const cellValue = getCellValue(rowIndex, cellIndex);
379
if (!rule.validator(cellValue)) {
380
if (!errors[rule.name]) {
381
errors[rule.name] = [];
382
}
383
errors[rule.name].push(rule.errorText);
384
}
385
});
386
}
387
388
return Object.keys(errors).length > 0 ? errors : null;
389
};
390
```
391
392
## Edit State Management
393
394
### Managing Edit State
395
396
```typescript
397
// Example state management for table editing
398
const [editingRows, setEditingRows] = useState<number[]>([]);
399
const [editValues, setEditValues] = useState<Record<string, any>>({});
400
const [openSelects, setOpenSelects] = useState<Record<string, boolean>>({});
401
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
402
403
// Helper functions
404
const startEditing = (rowIndex: number) => {
405
setEditingRows(prev => [...prev, rowIndex]);
406
// Initialize edit values with current row values
407
const row = rows[rowIndex];
408
row.cells.forEach((cell, cellIndex) => {
409
setEditValues(prev => ({
410
...prev,
411
[`${rowIndex}-${cellIndex}`]: cell
412
}));
413
});
414
};
415
416
const saveChanges = (rowIndex: number) => {
417
const validationErrors = validateRow(rowIndex);
418
if (!validationErrors) {
419
// Apply changes to the row data
420
const updatedRows = [...rows];
421
updatedRows[rowIndex].cells = updatedRows[rowIndex].cells.map((_, cellIndex) =>
422
editValues[`${rowIndex}-${cellIndex}`] || _
423
);
424
setRows(updatedRows);
425
setEditingRows(prev => prev.filter(idx => idx !== rowIndex));
426
clearEditState(rowIndex);
427
}
428
};
429
430
const cancelEditing = (rowIndex: number) => {
431
setEditingRows(prev => prev.filter(idx => idx !== rowIndex));
432
clearEditState(rowIndex);
433
};
434
435
const clearEditState = (rowIndex: number) => {
436
// Clear edit values for this row
437
const keysToRemove = Object.keys(editValues).filter(key =>
438
key.startsWith(`${rowIndex}-`)
439
);
440
setEditValues(prev => {
441
const newState = { ...prev };
442
keysToRemove.forEach(key => delete newState[key]);
443
return newState;
444
});
445
446
// Clear validation errors for this row
447
const errorKeysToRemove = Object.keys(validationErrors).filter(key =>
448
key.startsWith(`${rowIndex}-`)
449
);
450
setValidationErrors(prev => {
451
const newState = { ...prev };
452
errorKeysToRemove.forEach(key => delete newState[key]);
453
return newState;
454
});
455
};
456
```