Editor component
import Editor from '@monaco-editor/react';
import { useDebounce } from './useDebounce';
import { useEffect, useState } from 'react';
type Props = {
code: string;
onChange: (code: string) => void;
disabled?: boolean;
};
export const GraphqlCodeEditor = ({
onChange,
code,
disabled = false,
}: Props) => {
const [editorValue, setEditorValue] = useState(code);
const editorValueDebounced = useDebounce(editorValue, 500);
useEffect(() => {
onChange(editorValueDebounced);
}, [editorValueDebounced, onChange]);
useEffect(() => {
if (code !== editorValueDebounced) {
setEditorValue(code);
}
}, [code, editorValueDebounced]);
return (
<Editor
options={{
minimap: { enabled: false },
autoClosingBrackets: 'always',
readOnly: disabled,
}}
language="graphql"
value={editorValue}
onChange={(value) => {
if (value) {
setEditorValue(value);
}
}}
/>
);
};
useDebounce hook
import { useEffect, useState } from 'react';
export const useDebounce = <T>(value: T, delay: number) => {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
};
The code prop in the editor component is managed by a parent component. When e.g a user loads a new code snippet it's updated and the editor should load the new value. The editor should also debounce it's value so the onChange() function isn't called on every keypress.
The editor component above results in a loop where the component switches between the previous value and the new every 500ms.
How can I achieve this with the useDebounce hook?
This is closely related to this post I think, however it's using lodash debounce. I was hoping to achieve the same with the useDebounce hook.
The problem was this useEffect()
useEffect(() => {
if (code !== editorValueDebounced) {
setEditorValue(code);
}
}, [code, editorValueDebounced]);
Changing it to this fixed the problem
useEffect(() => {
setEditorValue(code);
}, [code]);