Skip to content
Kyle Truong

Using React-Quill with Formik

Tutorial, Frontend, JavaScript2 min read

Problem

While building a widget form, or a form that takes in data to build the widget you want, I came into some UX-related issues. Ideally, I wanted the user to have as many customization options as possible but the more options I added the longer the form got, and no one likes filling out forms. I started out with a simple text field, then I added another field to customize the font, then another for the position, and then color, and so on. By the end of it all I had at least 5 fields just to customize this one widget. There had to be a better way.

Investigation

I took a step back and saw what I needed was essentially a text editor, with such a tool I could refactor my 5+ fields into one. And so I found out about quill.js, a text editor with many features, good documentation, and simple integration into react with the react-quill library. There are also other solutions out there but there are many reasons to choose quill.js over them, more can be read about this at: https://quilljs.com/guides/why-quill/.

The documentation can seem a bit long but quill.js comes well-configured out of the box so you don't need to dive in and tinker with the API too much. The most important part, in my opinion, is the toolbar module that allows you to configure which options to add to your editor's toolbox.

https://quilljs.com/docs/modules/toolbar/

Proof of Concept

We start by installing the package:

npm install react-quill@beta

Then we import it into our component:

1import React, { useState } from 'react';
2import ReactQuill from 'react-quill';
3import 'react-quill/dist/quill.snow.css';
4
5export default function App() {
6 const [value, setValue] = useState('');
7
8 console.log('value: ', value);
9
10 return <ReactQuill theme="snow" value={value} onChange={setValue} />;
11}

And just like that it works:

React Quill in action

The out-of-the-box configuration works pretty well but if you want more specificity over what tools are in your editor's toolbox you can specify them by passing in props:

1export default function App() {
2 const [value, setValue] = useState('');
3
4 const modules = {
5 toolbar: [
6 [{ header: [1, 2, 3, false] }],
7 [{ font: Font.whitelist }],
8 ['bold', 'italic', 'underline', 'strike', 'link'],
9 [{ align: [] }],
10 [{ color: [] }, { background: [] }],
11 [{ script: 'sub' }, { script: 'super' }],
12 ['clean'],
13 ],
14 };
15
16 console.log('value: ', value);
17
18 return (
19 <ReactQuill
20 modules={modules}
21 theme="snow"
22 value={value}
23 onChange={setValue}
24 />
25 );
26}

The arrays are more for formatting than anything else. By separating the header and font objects into separate arrays we group them separately. Similarly, the ["bold", "italic", "underline", "strike", "link"] could be written with each value as a separate array if we didn't want to group them together. When empty arrays are used as values, such as { color: [] }, it means the library will use the default values set in the snow theme. A list of all the options can be found at: https://quilljs.com/docs/modules/toolbar/

Integrating React-Quill into Formik

We see from the above example that ReactQuill acts much like a controlled input. So, instead of storing the value with useState we could use the state stored within Formik. I personally like Formik's useField API so I created my own reusable widget that looks a bit like:

1export const TextEditorInput: React.FunctionComponent<TextEditorInputProps> = ({
2 name,
3 label = '',
4...
5}) => {
6 const [field, meta, helpers]: [
7 FieldInputProps<string>,
8 FieldMetaProps<string>,
9 FieldHelperProps<string>,
10 ] = useField<string>({ name });
11
12 const { onBlur, value } = field;
13 const { touched, error } = meta;
14 const { setValue } = helpers;
15
16 const formattedLabel = isOptional ? `${label} (optional)` : label;
17
18 /**
19 * Quill config.
20 */
21 const modules = {
22 toolbar: [
23 [{ font: Font.whitelist }],
24 [{ header: [1, 2, false] }],
25 [
26 'bold',
27 'italic',
28 'underline',
29 'strike',
30 { script: 'sub' },
31 { script: 'super' },
32 'link',
33 ],
34 [{ align: [] }],
35 ['clean'],
36 ],
37 };
38
39 /**
40 * Handlers
41 */
42 const handleChange = (value: string) => {
43 setValue(value);
44 };
45
46 /**
47 * https://github.com/zenoamaro/react-quill#methods
48 */
49 const handleEditorBlur = () => {
50 onBlur({ target: { name } });
51 };
52
53 return (
54 <div className={textEditorClasses}>
55 <ReactQuill
56 modules={modules}
57 theme="snow"
58 value={value}
59 onChange={handleChange}
60 onBlur={handleEditorBlur}
61 />
62 {error && touched && (
63 <div className="mt2">
64 <InlineError message={error} fieldID={name} />
65 </div>
66 )}
67 </div>
68 );
69};

And then I would use it in my form like:

1// form.component.tsx
2
3<Formik ... component={FormComponent} />
4
5// form-component.component.tsx
6---
7<form onSubmit={handleSubmit} onReset={handleReset}>
8---
9<TextEditorInput name={"message"} label={"message" />
10</form>

Bells and Whistles

The above code snippets cover the meat of the subject but if you wish to continue with extra features like adding custom fonts and emoji to the toolbar, I recommend reading the following:

1/**
2 * Emoji integration:
3 *
4 * https://github.com/contentco/quill-emoji/issues/106
5 * https://github.com/contentco/quill-emoji/issues/75
6 *
7 * Still incompelte, and requires importing a 7mb .png file.
8 *
9 * Custom fonts:
10 *
11 * https://github.com/zenoamaro/react-quill/issues/555
12 * https://github.com/zenoamaro/react-quill/issues/273
13 */

I had much success integrating custom fonts but none with the emoji. At the time of writing this, my attempts with integrating emoji with quill-emoji resulted in invisible icons and an inevitable downloading of a 7mb png file.

Conclusion

It's not everyday you'll benefit from using a text editor in your forms but if you will then I hope this post helps you.

© 2021 by Kyle Truong. All rights reserved.