react-quill
react-quill copied to clipboard
Initial onChange() event
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.
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)
}
}
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
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

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.
Arguments[2] would be 'user' if the change was user-initiated
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.
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.
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.
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.
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.
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.
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
Quill's
text-changeAPI 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
sourceparameter toonChange?
- https://quilljs.com/docs/api/#text-change
- https://github.com/zenoamaro/react-quill#props
Thank you sir, you saved my day!
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.
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 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 */);
}
}
Quill's
text-changeAPI 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
sourceparameter toonChange?
- 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.
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!
@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 elvalueaccesorio inicial que se envía a la pluma; si es verdadero, el primeroonChangede unaapifuente 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 */); } }
