react-quill icon indicating copy to clipboard operation
react-quill copied to clipboard

Initial onChange() event

Open priand opened this issue 8 years ago • 18 comments

React-Quill version

  • [ ] 1.1.0

An initial onChange() is thrown just after setting the value of the component using the value attribute. Even though the HTML is semantically identical, the actual string values (the one passed as the value and the one in the event) might be slightly different. Thus, when used in a form, the form turns instantly dirty as it thinks that the value changed. The onChange event should only be thrown after an actual change by a user, or having at least to flag to set this behavior when necessary. The regular HTML Input does not throw the event when the value is set.

priand avatar Aug 19 '17 22:08 priand

FYI, not particularly proud of it, but I circumvented the issue by creating my own component:

let preventOnChange = false;
class ReactQuillFix extends ReactQuill {
	componentWillReceiveProps(nextProps, nextState) {
        preventOnChange = true;
        try {
            super.componentWillReceiveProps(nextProps, nextState)
        } finally {
            preventOnChange = false;
        }
    }
}

and then, in the redux-form field binding, I forwarding the onChange event only if does not come from the property:

        let input2 = {
            ...input,
            onChange: function(content,delta,source,editor) { 
                if(!preventOnChange) input.onChange(content)
            }
        }

priand avatar Aug 19 '17 23:08 priand

Quill's text-change API distinguishes between programmatic and user-initiated changes, and React-Quill sends that information with the new HTML content in the onChange callback.

Have you looked into the source parameter to onChange?

  • https://quilljs.com/docs/api/#text-change
  • https://github.com/zenoamaro/react-quill#props

alexkrolick avatar Aug 20 '17 00:08 alexkrolick

I'm handling the following react event:

onChange: function(content,delta,source,editor)

But I don't see in the debugger how to know, from the arguments that it is a programmatic change image

Morever, most of the existing react form libraries, including redux-form, are giving you helpers for your controlled inputs through some properties values you should pass as is to your input field (value, onChange, onBlur...). See: http://redux-form.com/7.0.3/docs/api/Field.md/. If you use these helpers, then you don't even control the code behind onChange(). And these libraries will not check for some quill specific information.

Finally, the onChange() method does not comply with the ReactJS event model where the method should have only one object parameter (event) instead of 4 values (https://facebook.github.io/react/docs/forms.html). Hopefully, looks like redux form checks if the first argument is an event and, if not, it assumes it is a value. But it would be better to behave like all the other components. Note that event object can be extended with all the data you need.

priand avatar Aug 20 '17 16:08 priand

Arguments[2] would be 'user' if the change was user-initiated

screen shot 2017-08-20 at 10 11 01 am

Finally, the onChange() method does not comply with the ReactJS event model where the method should have only one object parameter (event) instead of 4 values

This is true; however, the DOM element underlying the editor does not support calling value(), unlike true form elements. You could mock the event value to comply with the Redux-Form API, but this would be misleading as the element would still not be usable with a form submit(). The Quill docs suggest integrating with forms by synchronizing with a hidden textarea. This "View Raw" example might also be relevant.

alexkrolick avatar Aug 20 '17 17:08 alexkrolick

Ok, I can check the api value. Although I'm not sure what happens with custom toolbar buttons, if an action then call the apis. I think it will be better if it can have a behavior closer to the other components. That will help the integration with third party libraries. Anyway, thanks for your hard work and your prompt replies! You did a great job.

priand avatar Aug 20 '17 22:08 priand

On a side note, there is something strange going on with the events. When I pass the handlers from redux-form, I found out that the onBlur() handler leads to a strange behavior: as soon as I click on a combo box in the toolbar (style, font...), the editor content is reset to empty. I believe the onblur is mostly to mark the field as visited and also run the validation. I have not debugged through it, so I don't know what's going on. It is basically fyi.

priand avatar Aug 21 '17 01:08 priand

OK, if you do discover some unusual behavior related to onBlur, please file a bug.

Thanks for reporting your concerns with the API. We do want to accommodate integrations with form libraries to the extent possible with abstractions over contenteditable.

alexkrolick avatar Aug 21 '17 15:08 alexkrolick

Hi there; I'm using redux-form; and I was surprised to see event/CHANGE on form loading... React-Quill is wrapped to be used as a redux-form component; and this issue explain our problem.

The changed value is forwarded to the higher-component only when user is source.

Regards; Thomas.

armetiz avatar Oct 24 '17 12:10 armetiz

The problem here is that Quill's onBlur emits a Range as its first argument. This means the redux-form will update to that value, which is empty.

To fix this you also need to catch Quill's onBlur like so:

<ReactQuill
  {...input} // from redux-form Field
  onChange={(newValue, delta, source) => {
    if (source === 'user') {
      input.onChange(newValue);
    }
  }}
  onBlur={(range, source, quill) => {
    input.onBlur(quill.getHTML());
  }}
/>

Hope this helps.

Gaya avatar Nov 09 '17 15:11 Gaya

We are using quill as text editor for mobile site and its giving below issue: Whenever we edit the description in quill text editor the images that are there in description gets removed, any idea how can we solve this problem? Thanks

KevalS avatar Feb 12 '18 10:02 KevalS

Quill's text-change API distinguishes between programmatic and user-initiated changes, and React-Quill sends that information with the new HTML content in the onChange callback.

Have you looked into the source parameter to onChange?

  • https://quilljs.com/docs/api/#text-change
  • https://github.com/zenoamaro/react-quill#props

Thank you sir, you saved my day!

nurbek-ab avatar May 08 '19 11:05 nurbek-ab

For anyone looking for working code (using Typescript). onChange is the part you'll want to include.

    <ReactQuill
      defaultValue={text}
      onChange={(text: string, delta: any, source: string, editor: any) => {
        if (source == 'user') {
          // place whatever function you want to execute when user types here:
          setText(text);
        }
      }}
      onBlur={onBlur}
      theme="snow"
      modules={{
        syntax: true,
        toolbar: toolbarOptions,
      }}
      formats={formatOptions}
    />

Issue can probably be closed now.

brandontle avatar Jun 27 '19 17:06 brandontle

Using the source is not good enough solution. We have image upload in our quill and when it adds an image, the source is api, but we need to invoke our onChange handlers to sync state. Problem is the moment we remove the if (source == 'user') { we get our initial event invocation again. Please remove the initial onChange invocation.

capaj avatar Feb 13 '20 00:02 capaj

@capaj I'm glad you pointed this out! I accounted for that by using a wrapper around react-quill. The wrapper checks the initial value prop being sent to the quill, if it's truthy the first onChange from an api source will be blocked. So:

const [blockApiChangeEvents, setBlockApiChangeEvents] = useState(!!props.value);

// ...

function handleChange(html, delta, source, editor) {
    // Blocking first api change
    if (source === 'api' && blockApiChangeEvents) {
        setBlockApiChangeEvents(false);
    } else {
        // Do things. Any additional api events will still fire here
        props.onChange(/* etc */);
    }
}

kevinparkerson avatar Apr 16 '20 20:04 kevinparkerson

Quill's text-change API distinguishes between programmatic and user-initiated changes, and React-Quill sends that information with the new HTML content in the onChange callback.

Have you looked into the source parameter to onChange?

  • https://quilljs.com/docs/api/#text-change
  • https://github.com/zenoamaro/react-quill#props

you're not wrong, but this is super clunky, counter-intuitive, and just needlessly complex in general for the developer... In my opinion maybe it should be refactored to have better dx.

Johnrobmiller avatar May 10 '22 21:05 Johnrobmiller

I totally agree that the default behavior is definitely not what we would expect at first. It caused me 1h to understand why I had a side effect due to this onChange event being fired at init. :+1: For a better DX please!

frinux avatar Jan 25 '24 15:01 frinux

@capaj @kevinparkerson Thank you very much, I was already going crazy not knowing why it was running by itself, you saved me from the psychiatric hospital. With the SOURCE variable, I have hope.

@capaj¡Me alegra que hayas señalado esto! Lo tomé en cuenta usando un contenedor alrededor react-quill. El contenedor verifica el valueaccesorio inicial que se envía a la pluma; si es verdadero, el primero onChangede una apifuente será bloqueado. Entonces:

const [blockApiChangeEvents, setBlockApiChangeEvents] = useState(!!props.value);

// ...

function handleChange(html, delta, source, editor) {
    // Blocking first api change
    if (source === 'api' && blockApiChangeEvents) {
        setBlockApiChangeEvents(false);
    } else {
        // Do things. Any additional api events will still fire here
        props.onChange(/* etc */);
    }
}

pablopissoni avatar Feb 22 '24 16:02 pablopissoni