— Tutorial, Frontend, JavaScript — 2 min read
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.
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/
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';45export default function App() {6 const [value, setValue] = useState('');78 console.log('value: ', value);910 return <ReactQuill theme="snow" value={value} onChange={setValue} />;11}
And just like that it works:
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('');34 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 };1516 console.log('value: ', value);1718 return (19 <ReactQuill20 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/
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 });1112 const { onBlur, value } = field;13 const { touched, error } = meta;14 const { setValue } = helpers;1516 const formattedLabel = isOptional ? `${label} (optional)` : label;1718 /**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 };3839 /**40 * Handlers41 */42 const handleChange = (value: string) => {43 setValue(value);44 };4546 /**47 * https://github.com/zenoamaro/react-quill#methods48 */49 const handleEditorBlur = () => {50 onBlur({ target: { name } });51 };5253 return (54 <div className={textEditorClasses}>55 <ReactQuill56 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.tsx23<Formik ... component={FormComponent} />45// form-component.component.tsx6---7<form onSubmit={handleSubmit} onReset={handleReset}>8---9<TextEditorInput name={"message"} label={"message" />10</form>
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/1065 * https://github.com/contentco/quill-emoji/issues/756 *7 * Still incompelte, and requires importing a 7mb .png file.8 *9 * Custom fonts:10 *11 * https://github.com/zenoamaro/react-quill/issues/55512 * https://github.com/zenoamaro/react-quill/issues/27313 */
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.
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.