Skip to content

Commit 8045dc9

Browse files
committed
chore: fix onchange action debounce dropped on last trigger
1 parent f500060 commit 8045dc9

File tree

2 files changed

+63
-14
lines changed

2 files changed

+63
-14
lines changed

packages/pluggableWidgets/rich-text-web/src/components/EditorWrapper.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { If } from "@mendix/widget-plugin-component-kit/If";
2+
import { useDebounceWithStatus } from "@mendix/widget-plugin-hooks/useDebounceWithStatus";
23
import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action";
3-
import { debounce } from "@mendix/widget-plugin-platform/utils/debounce";
44
import classNames from "classnames";
55
import Quill, { Range } from "quill";
66
import "quill/dist/quill.core.css";
@@ -12,7 +12,6 @@ import {
1212
useCallback,
1313
useContext,
1414
useEffect,
15-
useMemo,
1615
useRef,
1716
useState
1817
} from "react";
@@ -70,17 +69,17 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
7069

7170
const { isFullscreen } = globalState;
7271

73-
const [setAttributeValueDebounce] = useMemo(
74-
() =>
75-
debounce(string => {
76-
if (stringAttribute.value !== string) {
77-
stringAttribute.setValue(string);
78-
if (onChangeType === "onDataChange") {
79-
executeAction(onChange);
80-
}
72+
const [setAttributeValueDebounce] = useDebounceWithStatus(
73+
(string?: string) => {
74+
if (stringAttribute.value !== string) {
75+
stringAttribute.setValue(string);
76+
if (onChangeType === "onDataChange") {
77+
executeAction(onChange);
8178
}
82-
}, 200),
83-
[stringAttribute, onChange, onChangeType]
79+
}
80+
},
81+
200,
82+
onChange?.isExecuting ?? false
8483
);
8584

8685
const calculateCounts = useCallback(
@@ -128,15 +127,15 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
128127
}
129128
}
130129
// eslint-disable-next-line react-hooks/exhaustive-deps
131-
}, [quillRef.current]);
130+
}, [quillRef.current, onChange?.isExecuting]);
132131

133132
const onTextChange = useCallback(() => {
134133
if (stringAttribute.value !== quillRef?.current?.getSemanticHTML()) {
135134
setAttributeValueDebounce(quillRef?.current?.getSemanticHTML());
136135
}
137136
calculateCounts(quillRef.current);
138137
// eslint-disable-next-line react-hooks/exhaustive-deps
139-
}, [quillRef.current, stringAttribute, calculateCounts]);
138+
}, [quillRef.current, stringAttribute, calculateCounts, onChange?.isExecuting]);
140139

141140
const onSelectionChange = useCallback(
142141
(range: Range) => {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useCallback, useRef } from "react";
2+
3+
// this function will directly trigger function if action is not currently executing.
4+
// if there are executing actions, it will delay the execution
5+
// hence debounce based on executing status instead of pure timer.
6+
export const useDebounceWithStatus = <F extends (...args: any[]) => any>(
7+
func: F,
8+
waitFor: number,
9+
isExecuting: boolean
10+
): [F] => {
11+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
12+
const pendingActions = useRef<Parameters<F>>();
13+
14+
const abort = useCallback((): void => {
15+
if (timeoutRef.current !== null) {
16+
clearTimeout(timeoutRef.current);
17+
timeoutRef.current = null;
18+
}
19+
}, []);
20+
21+
const runPendingActions = useCallback(() => {
22+
abort();
23+
timeoutRef.current = setTimeout(() => {
24+
if (isExecuting) {
25+
runPendingActions();
26+
} else {
27+
// only run the last action
28+
const _args = pendingActions.current;
29+
if (_args) {
30+
// clear previous actions.
31+
pendingActions.current = undefined;
32+
func(..._args);
33+
}
34+
}
35+
}, waitFor);
36+
}, [abort, func, waitFor, isExecuting]);
37+
38+
const debounced = useCallback(
39+
(...args: Parameters<F>): void => {
40+
if (isExecuting) {
41+
pendingActions.current = args;
42+
runPendingActions();
43+
} else {
44+
func(...args);
45+
}
46+
},
47+
[runPendingActions, func, isExecuting]
48+
);
49+
return [debounced as F];
50+
};

0 commit comments

Comments
 (0)