diff --git a/components/lib/editor/Editor.js b/components/lib/editor/Editor.js index 15dbe138fb..efadd501c6 100644 --- a/components/lib/editor/Editor.js +++ b/components/lib/editor/Editor.js @@ -29,6 +29,8 @@ export const Editor = React.memo( const quill = React.useRef(null); const isQuillLoaded = React.useRef(false); + const [quillCreated, setQuillCreated] = React.useState(false); + useMountEffect(() => { if (!isQuillLoaded.current) { const configuration = { @@ -44,94 +46,109 @@ export const Editor = React.memo( if (QuillJS) { // GitHub #3097 loaded by script only - quill.current = new Quill(contentRef.current, configuration); - initQuill(); - - if (quill.current && quill.current.getModule('toolbar')) { - props.onLoad && props.onLoad(quill.current); - } + initQuill(new Quill(contentRef.current, configuration)); } else { - import('quill') - .then((module) => { - if (module && DomHandler.isExist(contentRef.current)) { - if (module.default) { - // webpack - quill.current = new module.default(contentRef.current, configuration); - } else { - // parceljs - quill.current = new module(contentRef.current, configuration); - } - - initQuill(); - } - }) - .then(() => { - if (quill.current && quill.current.getModule('toolbar')) { - props.onLoad && props.onLoad(quill.current); + import('quill').then((module) => { + if (module && DomHandler.isExist(contentRef.current)) { + let quillInstance; + + if (module.default) { + // webpack + quillInstance = new module.default(contentRef.current, configuration); + } else { + // parceljs + quillInstance = new module(contentRef.current, configuration); } - }); + + initQuill(quillInstance); + } + }); } isQuillLoaded.current = true; } }); - const initQuill = () => { - if (props.value) { - quill.current.setContents(quill.current.clipboard.convert(props.value)); + const onTextChange = (delta, oldContents, source) => { + let firstChild = contentRef.current.children[0]; + let html = firstChild ? firstChild.innerHTML : null; + let text = quill.current.getText(); + + if (html === '


') { + html = null; } - quill.current.on('text-change', (delta, oldContents, source) => { - let firstChild = contentRef.current.children[0]; - let html = firstChild ? firstChild.innerHTML : null; - let text = quill.current.getText(); + // GitHub #2271 prevent infinite loop on clipboard paste of HTML + if (source === 'api') { + const htmlValue = contentRef.current.children[0]; + const editorValue = document.createElement('div'); - if (html === '


') { - html = null; - } + editorValue.innerHTML = props.value || ''; - // GitHub #2271 prevent infinite loop on clipboard paste of HTML - if (source === 'api') { - const htmlValue = contentRef.current.children[0]; - const editorValue = document.createElement('div'); + // this is necessary because Quill rearranged style elements + if (DomHandler.isEqualElement(htmlValue, editorValue)) { + return; + } + } - editorValue.innerHTML = props.value || ''; + if (props.maxLength) { + const length = quill.current.getLength(); - // this is necessary because Quill rearranged style elements - if (DomHandler.isEqualElement(htmlValue, editorValue)) { - return; - } + if (length > props.maxLength) { + quill.current.deleteText(props.maxLength, length); } + } - if (props.maxLength) { - const length = quill.current.getLength(); + if (props.onTextChange) { + props.onTextChange({ + htmlValue: html, + textValue: text, + delta: delta, + source: source + }); + } + }; - if (length > props.maxLength) { - quill.current.deleteText(props.maxLength, length); - } - } + const onSelectionChange = (range, oldRange, source) => { + if (props.onSelectionChange) { + props.onSelectionChange({ + range: range, + oldRange: oldRange, + source: source + }); + } + }; - if (props.onTextChange) { - props.onTextChange({ - htmlValue: html, - textValue: text, - delta: delta, - source: source - }); - } - }); - - quill.current.on('selection-change', (range, oldRange, source) => { - if (props.onSelectionChange) { - props.onSelectionChange({ - range: range, - oldRange: oldRange, - source: source - }); - } - }); + const initQuill = (quillInstance) => { + quill.current = quillInstance; + + if (props.value) { + quill.current.setContents(quill.current.clipboard.convert(props.value)); + } + + setQuillCreated(true); }; + useUpdateEffect(() => { + if (quillCreated) { + quill.current.on('text-change', onTextChange); + quill.current.on('selection-change', onSelectionChange); + + return () => { + quill.current.off('text-change', onTextChange); + quill.current.off('selection-change', onSelectionChange); + }; + } + }); + + useUpdateEffect(() => { + if (quillCreated) { + if (quill.current && quill.current.getModule('toolbar')) { + props.onLoad && props.onLoad(quill.current); + } + } + }, [quillCreated]); + useUpdateEffect(() => { if (quill.current && !quill.current.hasFocus()) { props.value ? quill.current.setContents(quill.current.clipboard.convert(props.value)) : quill.current.setText(''); diff --git a/components/lib/editor/Editor.spec.js b/components/lib/editor/Editor.spec.js new file mode 100644 index 0000000000..bfa7481cd1 --- /dev/null +++ b/components/lib/editor/Editor.spec.js @@ -0,0 +1,78 @@ +import { useState, useRef, StrictMode } from 'react'; +import '@testing-library/jest-dom'; +import { render, fireEvent, cleanup, waitFor } from '@testing-library/react'; +import { Editor } from './Editor'; + +afterEach(cleanup); + +describe('Editor', () => { + // https://github.com/primefaces/primereact/issues/6067 + test('onTextChange handler does not reflect updated props', async () => { + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + + function BasicDemo() { + const [state, setState] = useState(1); + + return ( + <> + + state is: {state} + + + ); + } + + function BasicDemo1({ state }) { + const [text, setText] = useState(''); + + const ref = useRef(); + + return ( +
+ + { + // eslint-disable-next-line no-console + console.log(`Editor. state is:${state}`); + setText(e.htmlValue); + }} + onLoad={(q) => { + q.setText('hi'); + }} + /> +
+ ); + } + + const { getByTestId } = render(); + + fireEvent.click(getByTestId('update-state')); + expect(getByTestId('state').textContent).toBe('2'); + + await waitFor(() => { + expect(consoleSpy).toHaveBeenCalledWith('Editor. state is:2'); + }); + + fireEvent.click(getByTestId('update-state')); + fireEvent.click(getByTestId('update-editor')); + expect(getByTestId('state').textContent).toBe('3'); + await waitFor(() => { + expect(consoleSpy).toHaveBeenCalledWith('Editor. state is:3'); + }); + consoleSpy.mockRestore(); + }); +});