Skip to content

Commit e8c4576

Browse files
authored
ENG-790 - Referencing an EVD or RES node on canvas is creating a duplicate node with additional tag (#390)
* Refactor LabelDialogAutocomplete to use async/await for fetching options - Replaced promise chaining with async/await and try/catch for improved error handling. - Simplified the fetching logic for main and referenced node options. - Updated useEffect dependencies for better clarity and performance. - Changed referencedNode retrieval to use useMemo for optimization. * Enhance LabelDialogAutocomplete with request ID tracking and cleanup - Introduced request ID tracking to prevent stale state updates during asynchronous fetching. - Added cleanup logic in useEffect to manage component lifecycle and avoid memory leaks. - Improved error handling to ensure console errors are logged only for the latest request. - Maintained existing async/await structure for fetching options, enhancing clarity and performance.
1 parent 6bcad0d commit e8c4576

File tree

1 file changed

+59
-50
lines changed

1 file changed

+59
-50
lines changed

apps/roam/src/components/canvas/LabelDialog.tsx

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const LabelDialogAutocomplete = ({
5252
format: string;
5353
label: string;
5454
}) => {
55+
const requestIdRef = useRef(0);
5556
const [isLoading, setIsLoading] = useState(false);
5657
const [options, setOptions] = useState<Result[]>([]);
5758
const [referencedNodeOptions, setReferencedNodeOptions] = useState<Result[]>(
@@ -61,57 +62,66 @@ const LabelDialogAutocomplete = ({
6162
const [isAddReferencedNode, setAddReferencedNode] = useState(false);
6263
const [isEditExistingLabel, setIsEditExistingLabel] = useState(false);
6364
const [content, setContent] = useState(label);
64-
6565
useEffect(() => {
66+
let alive = true;
67+
const req = ++requestIdRef.current;
6668
setIsLoading(true);
67-
const conditionUid = window.roamAlphaAPI.util.generateUID();
69+
const fetchOptions = async () => {
70+
try {
71+
// Fetch main options
72+
if (nodeType) {
73+
const conditionUid = window.roamAlphaAPI.util.generateUID();
74+
const results = await fireQuery({
75+
returnNode: "node",
76+
selections: [],
77+
conditions: [
78+
{
79+
source: "node",
80+
relation: "is a",
81+
target: nodeType,
82+
uid: conditionUid,
83+
type: "clause",
84+
},
85+
],
86+
});
87+
if (requestIdRef.current === req && alive) setOptions(results);
88+
}
6889

69-
setTimeout(() => {
70-
if (nodeType) {
71-
void fireQuery({
72-
returnNode: "node",
73-
selections: [],
74-
conditions: [
75-
{
76-
source: "node",
77-
relation: "is a",
78-
target: nodeType,
79-
uid: conditionUid,
80-
type: "clause",
81-
},
82-
],
83-
}).then((results) => {
84-
setOptions(results);
85-
});
86-
}
87-
if (referencedNode) {
88-
void fireQuery({
89-
returnNode: "node",
90-
selections: [],
91-
conditions: [
92-
{
93-
source: "node",
94-
relation: "is a",
95-
target: referencedNode.nodeType,
96-
uid: conditionUid,
97-
type: "clause",
98-
},
99-
],
100-
}).then((results) => {
101-
setReferencedNodeOptions(results);
102-
setIsLoading(false);
103-
});
104-
} else {
105-
setIsLoading(false);
90+
// Fetch referenced node options if needed
91+
if (isAddReferencedNode && referencedNode) {
92+
const conditionUid = window.roamAlphaAPI.util.generateUID();
93+
const results = await fireQuery({
94+
returnNode: "node",
95+
selections: [],
96+
conditions: [
97+
{
98+
source: "node",
99+
relation: "is a",
100+
target: referencedNode.nodeType,
101+
uid: conditionUid,
102+
type: "clause",
103+
},
104+
],
105+
});
106+
if (requestIdRef.current === req && alive) {
107+
setReferencedNodeOptions(results);
108+
}
109+
}
110+
} catch (error) {
111+
if (requestIdRef.current === req && alive) {
112+
console.error("Error fetching options:", error);
113+
}
114+
} finally {
115+
if (requestIdRef.current === req && alive) setIsLoading(false);
106116
}
107-
}, 100);
108-
}, [
109-
nodeType,
110-
referencedNode?.nodeType,
111-
setOptions,
112-
setReferencedNodeOptions,
113-
referencedNode,
114-
]);
117+
};
118+
119+
void fetchOptions();
120+
return () => {
121+
alive = false;
122+
};
123+
}, [nodeType, isAddReferencedNode, referencedNode]);
124+
115125
const inputDivRef = useRef<HTMLDivElement>(null);
116126
useEffect(() => {
117127
if (isAddReferencedNode && inputDivRef.current) {
@@ -324,7 +334,7 @@ const LabelDialog = ({
324334
const [loading, setLoading] = useState(false);
325335
const isCreateCanvasNode = !isLiveBlock(initialUid);
326336
const { format } = discourseContext.nodes[nodeType];
327-
const getReferencedNodeInFormat = () => {
337+
const referencedNode = useMemo(() => {
328338
const regex = /{([\w\d-]*)}/g;
329339
const matches = [...format.matchAll(regex)];
330340

@@ -342,8 +352,7 @@ const LabelDialog = ({
342352
}
343353

344354
return null;
345-
};
346-
const referencedNode = getReferencedNodeInFormat();
355+
}, [format, discourseContext.nodes]);
347356

348357
const renderCalloutText = () => {
349358
let title = "Please provide a label";

0 commit comments

Comments
 (0)