I am trying to debounce a call to update codemirror. It kind of "works" but the issue is the editor's value is not updated (at all) until after the debounce call. So if I even press the spacebar, the cursor position does not update until the debounced call has been completed.
The debounced call is an action creator which flows through multiple sagas, reducers and finally updates the component which produces noticeable lag in the app. Is there a way to do this so the user can keep typing without this lag?
EDIT:
this question sums up my issue pretty well: code mirror takes the value from the state, while debounce prevents the state to trigger.
Code:
const cmEditor = ({ selectedFile, updateCode }) => {
const debounced = useCallback(
_.debounce((code, selectedFile) => { updateCode({ code, selectedFile }); }, 1000),
[],
);
const update = (editor, data, code) => {
debounced(code, selectedFile);
};
return (
<Container>
<CodeMirror
value={selectedFile.content}
options={{ ...CODE_MIRROR_DEFAULT_OPTIONS, mode: getCodeMode(EDITORS.CODE_MIRROR.value, selectedFile.mimeType) }}
onBeforeChange={(editor, data, code) => update(editor, data, code)}
/>
</Container>
);
};
cmEditor.propTypes = {
selectedFile: PropTypes.object.isRequired,
updateCode: PropTypes.func.isRequired,
};
export default cmEditor;
EDIT 2:
Here is my implementation using local state and an idle timer. Not quite working but almost:
const cmEditor = ({ selectedFile, updateCode }) => {
const [idleTimer, setIdleTimer] = useState(1);
const [localCode, setLocalCode] = useState(selectedFile.content);
useEffect(() => {
setLocalCode(selectedFile.content);
}, [selectedFile.content]);
// Add event listeners
useEffect(() => {
window.addEventListener('mousemove', resetIdleTimer);
window.addEventListener('keypress', resetIdleTimer);
// Remove event listeners on cleanup
return () => {
window.removeEventListener('mousemove', resetIdleTimer);
window.removeEventListener('keypress', resetIdleTimer);
};
}, []); // Empty array ensures that effect is only run on mount and unmount
useEffect(() => {
let timer = null;
timer = setTimeout(() => {
if (idleTimer === 0) {
handleUpdate();
clearTimeout(timer);
// resetIdleTimer();
} else {
setIdleTimer(idleTimer - 1);
}
}, 1000);
return () => clearTimeout(timer);
}, [idleTimer]);
const resetIdleTimer = () => setIdleTimer(DEFAULT_IDLE_TIMEOUT);
const handleUpdate = () => updateCode({ code: localCode, selectedFile });
return (
<Container>
<CodeMirror
value={localCode}
options={{ ...CODE_MIRROR_DEFAULT_OPTIONS, mode: getCodeMode(EDITORS.CODE_MIRROR.value, selectedFile.mimeType) }}
onBeforeChange={(editor, data, code) => setLocalCode(code)}
/>
</Container>
);
};
I am not too familiar with React, but I assume that the changes will only be visible after the
update
function completes, which includes the debounce time. You could make the function asynchronous by marking the update function withasync
, though I don't know if this will have any side effects in your framework:As an alternative you could consider using the
onChange
trigger instead ofonBeforeChange
, as it will first process the user's interaction before executing your statement.