diff --git a/packages/compass-crud/src/components/json-editor.tsx b/packages/compass-crud/src/components/json-editor.tsx index 767c54160d8..2dda284dba4 100644 --- a/packages/compass-crud/src/components/json-editor.tsx +++ b/packages/compass-crud/src/components/json-editor.tsx @@ -76,9 +76,26 @@ const JSONEditor: React.FunctionComponent = ({ const [expanded, setExpanded] = useState(doc.expanded); const [editing, setEditing] = useState(doc.editing); const [deleting, setDeleting] = useState(doc.markedForDeletion); - const [value, setValue] = useState(() => doc.toEJSON()); + const [value, setValue] = useState( + () => doc.modifiedEJSONString ?? doc.toEJSON() + ); const [initialValue] = useState(() => doc.toEJSON()); const [containsErrors, setContainsErrors] = useState(false); + const setModifiedEJSONStringRef = useRef<(value: string | null) => void>( + doc.setModifiedEJSONString.bind(doc) + ); + setModifiedEJSONStringRef.current = doc.setModifiedEJSONString.bind(doc); + + useEffect(() => { + const setModifiedEJSONString = setModifiedEJSONStringRef.current; + return () => { + // When this component is used in virtualized list, the editor is + // unmounted on scroll and if the user is editing the document, the + // editor value is lost. This is a way to keep track of the editor + // value when the it's unmounted and is restored on next mount. + setModifiedEJSONString(editing ? value : null); + }; + }, [value, editing]); const handleCopy = useCallback(() => { copyToClipboard?.(doc); diff --git a/packages/compass-crud/src/components/virtualized-document-json-view.spec.tsx b/packages/compass-crud/src/components/virtualized-document-json-view.spec.tsx index 0c8060d267a..37672979171 100644 --- a/packages/compass-crud/src/components/virtualized-document-json-view.spec.tsx +++ b/packages/compass-crud/src/components/virtualized-document-json-view.spec.tsx @@ -12,6 +12,10 @@ import { import { type VirtualListRef } from '@mongodb-js/compass-components'; import VirtualizedDocumentJsonView from './virtualized-document-json-view'; +import { + getCodemirrorEditorValue, + setCodemirrorEditorValue, +} from '@mongodb-js/compass-editor'; const createBigDocument = (variable: number) => new HadronDocument({ @@ -168,4 +172,44 @@ describe('VirtualizedDocumentJsonView', function () { expect(() => within(documentElement).getByText('Cancel')).to.throw; expect(() => within(documentElement).getByText('Replace')).to.throw; }); + + it('preserves the edit state of document when a document goes out of visible viewport when scrolling', async function () { + const bigDocuments = Array.from({ length: 20 }, (_, idx) => + createBigDocument(idx) + ); + const listRef: VirtualListRef = React.createRef(); + render( + + ); + + let [firstDocumentElement] = screen.getAllByTestId('editable-json'); + // Trigger the edit state (we only set the edited value if the document is being edited) + userEvent.click(within(firstDocumentElement).getByLabelText('Edit')); + + let cmEditor = firstDocumentElement.querySelector( + '[data-codemirror="true"]' + ); + await setCodemirrorEditorValue(cmEditor, '{value: "edited"}'); + + // Scroll down and then scroll back up + act(() => { + listRef.current?.scrollToItem(15); + }); + act(() => { + listRef.current?.scrollToItem(0); + }); + + [firstDocumentElement] = screen.getAllByTestId('editable-json'); + cmEditor = firstDocumentElement.querySelector('[data-codemirror="true"]'); + + const value = getCodemirrorEditorValue(cmEditor); + expect(value).to.equal('{value: "edited"}'); + }); }); diff --git a/packages/hadron-document/src/document.ts b/packages/hadron-document/src/document.ts index 35c51c3995d..1670ca3ccbd 100644 --- a/packages/hadron-document/src/document.ts +++ b/packages/hadron-document/src/document.ts @@ -50,6 +50,9 @@ export class Document extends EventEmitter { maxVisibleElementsCount = DEFAULT_VISIBLE_ELEMENTS; editing = false; markedForDeletion = false; + // This is used to store the changed EJSON string when the document is modified + // via the JSONEditor. + modifiedEJSONString: string | null = null; /** * Send cancel event. @@ -456,6 +459,7 @@ export class Document extends EventEmitter { finishEditing() { if (this.editing) { this.editing = false; + this.setModifiedEJSONString(null); this.emit(DocumentEvents.EditingFinished); } } @@ -503,6 +507,10 @@ export class Document extends EventEmitter { onRemoveError(error: Error) { this.emit('remove-error', error.message); } + + setModifiedEJSONString(ejson: string | null) { + this.modifiedEJSONString = ejson; + } } export default Document;