From d69d02ce105ec3073d8ac29518371180672c54e2 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Thu, 24 Oct 2019 18:58:28 -0300 Subject: [PATCH] Convert RichTextInput to a functional component --- packages/ra-input-rich-text/src/index.js | 253 +++++++++--------- packages/ra-input-rich-text/src/index.spec.js | 27 +- 2 files changed, 135 insertions(+), 145 deletions(-) diff --git a/packages/ra-input-rich-text/src/index.js b/packages/ra-input-rich-text/src/index.js index a9c2636f339..9d594e3473a 100644 --- a/packages/ra-input-rich-text/src/index.js +++ b/packages/ra-input-rich-text/src/index.js @@ -1,153 +1,142 @@ import debounce from 'lodash/debounce'; -import React, { Component } from 'react'; +import React, { useRef, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import Quill from 'quill'; -import { addField, FieldTitle } from 'ra-core'; +import { useInput, FieldTitle } from 'ra-core'; import { InputHelperText } from 'ra-ui-materialui'; import { FormHelperText, FormControl, InputLabel } from '@material-ui/core'; -import { withStyles } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import styles from './styles'; -export class RichTextInput extends Component { - lastValueChange = null; - - static propTypes = { - addLabel: PropTypes.bool.isRequired, - classes: PropTypes.object, - input: PropTypes.object, - label: PropTypes.string, - meta: PropTypes.object, - options: PropTypes.object, - source: PropTypes.string, - toolbar: PropTypes.oneOfType([ - PropTypes.array, - PropTypes.bool, - PropTypes.shape({ - container: PropTypes.array, - handlers: PropTypes.object, - }), - ]), - fullWidth: PropTypes.bool, - configureQuill: PropTypes.func, - }; - - static defaultProps = { - addLabel: true, - options: {}, // Quill editor options - record: {}, - toolbar: true, - fullWidth: true, - }; - - componentDidMount() { - const { - input: { value }, - toolbar, - options, - } = this.props; - - this.quill = new Quill(this.divRef, { +const useStyles = makeStyles(styles); + +const RichTextInput = ({ + options = {}, // Quill editor options + record = {}, + toolbar = true, + fullWidth = true, + configureQuill, + helperText = false, + label, + source, + resource, + variant, + margin = 'dense', + ...rest +}) => { + const classes = useStyles(); + const quillInstance = useRef(); + const divRef = useRef(); + const editor = useRef(); + + const { + id, + isRequired, + input: { value, onChange }, + meta: { touched, error }, + } = useInput({ source, ...rest }); + + const lastValueChange = useRef(value); + + const onTextChange = useCallback( + debounce(() => { + const value = + editor.current.innerHTML === '


' + ? '' + : editor.current.innerHTML; + lastValueChange.current = value; + onChange(value); + }, 500), + [] + ); + + useEffect(() => { + quillInstance.current = new Quill(divRef.current, { modules: { toolbar, clipboard: { matchVisual: false } }, theme: 'snow', ...options, }); - if (this.props.configureQuill) { - this.props.configureQuill(this.quill); + if (configureQuill) { + configureQuill(quillInstance.current); } - this.quill.setContents(this.quill.clipboard.convert(value)); - - this.editor = this.divRef.querySelector('.ql-editor'); - this.quill.on('text-change', this.onTextChange); - } + quillInstance.current.setContents( + quillInstance.current.clipboard.convert(value) + ); - componentDidUpdate() { - if (this.lastValueChange !== this.props.input.value) { - const selection = this.quill.getSelection(); - this.quill.setContents( - this.quill.clipboard.convert(this.props.input.value) + editor.current = divRef.current.querySelector('.ql-editor'); + quillInstance.current.on('text-change', onTextChange); + + return () => { + quillInstance.current.off('text-change', onTextChange); + onTextChange.cancel(); + quillInstance.current = null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (lastValueChange.current !== value) { + const selection = quillInstance.current.getSelection(); + quillInstance.current.setContents( + quillInstance.current.clipboard.convert(value) ); - if (selection && this.quill.hasFocus()) { - this.quill.setSelection(selection); + if (selection && quillInstance.current.hasFocus()) { + quillInstance.current.setSelection(selection); } } - } - - componentWillUnmount() { - this.quill.off('text-change', this.onTextChange); - this.onTextChange.cancel(); - this.quill = null; - } - - onTextChange = debounce(() => { - const value = - this.editor.innerHTML === '


' - ? '' - : this.editor.innerHTML; - this.lastValueChange = value; - this.props.input.onChange(value); - }, 500); - - updateDivRef = ref => { - this.divRef = ref; - }; - - render() { - const { - label, - source, - resource, - isRequired, - id, - classes = {}, - margin = 'dense', - variant, - } = this.props; - const { touched, error, helperText = false } = this.props.meta; - return ( - - {label !== '' && label !== false && ( - - - - )} -
- {helperText || (touched && error) ? ( - - - - ) : null} - - ); - } -} - -const RichTextInputWithField = addField(withStyles(styles)(RichTextInput)); + }, [value]); + + return ( + + {label !== '' && label !== false && ( + + + + )} +
+ {helperText || (touched && error) ? ( + + + + ) : null} + + ); +}; -RichTextInputWithField.defaultProps = { - fullWidth: true, +RichTextInput.propTypes = { + label: PropTypes.string, + options: PropTypes.object, + source: PropTypes.string, + toolbar: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.bool, + PropTypes.shape({ + container: PropTypes.array, + handlers: PropTypes.object, + }), + ]), + fullWidth: PropTypes.bool, + configureQuill: PropTypes.func, }; -export default RichTextInputWithField; + +export default RichTextInput; diff --git a/packages/ra-input-rich-text/src/index.spec.js b/packages/ra-input-rich-text/src/index.spec.js index efe1a64665a..46a30d7d6e6 100644 --- a/packages/ra-input-rich-text/src/index.spec.js +++ b/packages/ra-input-rich-text/src/index.spec.js @@ -1,8 +1,9 @@ import React from 'react'; import debounce from 'lodash/debounce'; import { render, fireEvent, waitForElement } from '@testing-library/react'; +import { Form } from 'react-final-form'; -import { RichTextInput } from './index'; +import RichTextInput from './index'; let container; @@ -30,12 +31,12 @@ describe('RichTextInput', () => { const handleChange = jest.fn(); debounce.mockImplementation(fn => fn); const { getByTestId, rerender } = render( - test

', - onChange: handleChange, - }} - meta={{ error: null }} +
test

' }} + onSubmit={jest.fn()} + render={() => ( + + )} /> ); const quillNode = await waitForElement(() => { @@ -50,12 +51,12 @@ describe('RichTextInput', () => { jest.runOnlyPendingTimers(); rerender( - test1

', - onChange: handleChange, - }} - meta={{ error: null }} + test1

' }} + onSubmit={jest.fn()} + render={() => ( + + )} /> );