Skip to content

Commit 46027f5

Browse files
authored
Fix ReactKey issues with multiple new annotation rows (#1240)
## Description <!-- Please provide a brief description of the changes made in this pull request. Include any relevant context or reasoning for the changes. --> Fixes a bug where React Keys for new annotation rows were unstable due to being based on the row `index` in the array. This would cause issues when, for example, a row was delete or submitted, as all other rows would shift upwards and inherit the info of the row above. The solution is to assign each new row a unique id specifically for use by React. I also did a brief bit of tidying up while I was there, by introducing a proper `NewRow` type. ## Related Issue and Pull requests <!-- Link to any related issues using the format #<issue-number> --> Closes Shopify/oasis-frontend#336 ## Type of Change <!-- Please delete options that are not relevant --> - [x] Bug fix ## Checklist <!-- Please ensure the following are completed before submitting the PR --> - [ ] I have tested this does not break current pipelines / runs functionality - [ ] I have tested the changes on staging ## Screenshots (if applicable) <!-- Include any screenshots that might help explain the changes or provide visual context --> ## Test Instructions <!-- Detail steps and prerequisites for testing the changes in this PR --> - Confirm you can now edit/delete/navigate multiple new annotation rows without results being mixed up or erroneously deleted. ## Additional Comments <!-- Add any additional context or information that reviewers might need to know regarding this PR -->
1 parent 1bcf398 commit 46027f5

File tree

3 files changed

+42
-30
lines changed

3 files changed

+42
-30
lines changed

src/components/shared/ReactFlow/FlowCanvas/TaskNode/AnnotationsEditor/AnnotationsEditor.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import type { AnnotationConfig, Annotations } from "@/types/annotations";
66

77
import { AnnotationsInput } from "./AnnotationsInput";
88
import { COMPUTE_RESOURCES } from "./ComputeResourcesEditor";
9-
import { NewAnnotationRow } from "./NewAnnotationRow";
9+
import {
10+
NewAnnotationRow,
11+
type NewAnnotationRowData,
12+
} from "./NewAnnotationRow";
1013

1114
interface AnnotationsEditorProps {
1215
annotations: Annotations;
1316
onSave: (key: string, value: string) => void;
1417
onRemove: (key: string) => void;
15-
newRows: Array<{ key: string; value: string }>;
16-
onNewRowBlur: (idx: number, newRow: { key: string; value: string }) => void;
17-
onRemoveNewRow: (idx: number) => void;
18+
newRows: Array<NewAnnotationRowData>;
19+
onNewRowBlur: (newRow: NewAnnotationRowData) => void;
20+
onRemoveNewRow: (newRow: NewAnnotationRowData) => void;
1821
onAddNewRow: () => void;
1922
}
2023

@@ -96,11 +99,11 @@ export const AnnotationsEditor = ({
9699

97100
{newRows.map((row, idx) => (
98101
<NewAnnotationRow
99-
key={row.key + idx}
102+
key={row.id}
100103
row={row}
101104
autofocus={idx === newRows.length - 1}
102-
onBlur={(newRow) => onNewRowBlur(idx, newRow)}
103-
onRemove={() => onRemoveNewRow(idx)}
105+
onBlur={onNewRowBlur}
106+
onRemove={onRemoveNewRow}
104107
/>
105108
))}
106109
</div>

src/components/shared/ReactFlow/FlowCanvas/TaskNode/AnnotationsEditor/AnnotationsSection.tsx

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { TaskSpec } from "@/utils/componentSpec";
88

99
import { AnnotationsEditor } from "./AnnotationsEditor";
1010
import { ComputeResourcesEditor } from "./ComputeResourcesEditor";
11+
import type { NewAnnotationRowData } from "./NewAnnotationRow";
1112

1213
interface AnnotationsSectionProps {
1314
taskSpec: TaskSpec;
@@ -27,43 +28,41 @@ export const AnnotationsSection = ({
2728
});
2829

2930
// Track new rows separately until they have a key
30-
const [newRows, setNewRows] = useState<Array<{ key: string; value: string }>>(
31-
[],
32-
);
31+
const [newRows, setNewRows] = useState<Array<NewAnnotationRowData>>([]);
3332

3433
const handleAddNewRow = useCallback(() => {
35-
setNewRows((rows) => [...rows, { key: "", value: "" }]);
34+
const newRow = { id: Date.now().toString(), key: "", value: "" };
35+
setNewRows((rows) => [...rows, newRow]);
3636
}, []);
3737

38-
const handleRemoveNewRow = useCallback((idx: number) => {
39-
setNewRows((rows) => rows.filter((_, i) => i !== idx));
38+
const handleRemoveNewRow = useCallback((newRow: NewAnnotationRowData) => {
39+
setNewRows((rows) => rows.filter((row) => row.id !== newRow.id));
4040
}, []);
4141

4242
const handleNewRowBlur = useCallback(
43-
(idx: number, newRow: { key: string; value: string }) => {
44-
const updatedRows = [...newRows];
45-
updatedRows[idx] = { ...updatedRows[idx], ...newRow };
46-
47-
const row = updatedRows[idx];
48-
49-
if (row.key.trim() && !(row.key in annotations)) {
43+
(newRow: NewAnnotationRowData) => {
44+
if (newRow.key.trim() && !(newRow.key in annotations)) {
5045
const newAnnotations = {
5146
...annotations,
52-
[row.key]: row.value,
47+
[newRow.key]: newRow.value,
5348
};
5449
setAnnotations(newAnnotations);
5550
onApply(newAnnotations);
5651

57-
setNewRows((rows) => rows.filter((_, i) => i !== idx));
52+
setNewRows((rows) => rows.filter((row) => row.id !== newRow.id));
5853
} else {
59-
if (row.key.trim() && row.key in annotations) {
54+
if (newRow.key.trim() && newRow.key in annotations) {
6055
notify("Annotation key already exists", "warning");
6156
}
6257

63-
setNewRows(updatedRows);
58+
setNewRows((rows) =>
59+
rows.map((row) =>
60+
row.id === newRow.id ? { ...row, ...newRow } : row,
61+
),
62+
);
6463
}
6564
},
66-
[newRows, annotations, onApply],
65+
[annotations, onApply, notify],
6766
);
6867

6968
const handleRemove = useCallback(

src/components/shared/ReactFlow/FlowCanvas/TaskNode/AnnotationsEditor/NewAnnotationRow.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ import { Icon } from "@/components/ui/icon";
55
import { Input } from "@/components/ui/input";
66
import { InlineStack } from "@/components/ui/layout";
77

8+
export type NewAnnotationRowData = {
9+
id: string;
10+
key: string;
11+
value: string;
12+
};
13+
814
interface NewAnnotationRowProps {
9-
row: { key: string; value: string };
15+
row: NewAnnotationRowData;
1016
autofocus: boolean;
11-
onBlur: (row: { key: string; value: string }) => void;
12-
onRemove: () => void;
17+
onBlur: (row: NewAnnotationRowData) => void;
18+
onRemove: (row: NewAnnotationRowData) => void;
1319
}
1420

1521
export const NewAnnotationRow = ({
@@ -21,7 +27,7 @@ export const NewAnnotationRow = ({
2127
const [key, setKey] = useState(row.key);
2228
const [value, setValue] = useState(row.value);
2329

24-
const newRow = useMemo(() => ({ key, value }), [key, value]);
30+
const newRow = useMemo(() => ({ ...row, key, value }), [row, key, value]);
2531

2632
const handleRowBlur = useCallback(
2733
(e: FocusEvent<HTMLDivElement>) => {
@@ -32,6 +38,10 @@ export const NewAnnotationRow = ({
3238
[newRow, onBlur],
3339
);
3440

41+
const handleRowRemove = useCallback(() => {
42+
onRemove(newRow);
43+
}, [newRow, onRemove]);
44+
3545
return (
3646
<div onBlur={handleRowBlur}>
3747
<InlineStack blockAlign="center" gap="2" className="mt-2">
@@ -48,7 +58,7 @@ export const NewAnnotationRow = ({
4858
onChange={(e) => setValue(e.target.value)}
4959
className="flex-1"
5060
/>
51-
<Button variant="ghost" size="icon" onClick={onRemove}>
61+
<Button variant="ghost" size="icon" onClick={handleRowRemove}>
5262
<Icon name="Trash" className="text-destructive" />
5363
</Button>
5464
</InlineStack>

0 commit comments

Comments
 (0)