Building a Blog Editor with React MUI and Quill.js
In this tutorial, we’ll walk through the process of creating a blog editor using React, Material-UI (MUI), and the Quill.js rich text editor. This editor will allow you to write and format blog posts easily. We’ll explore the code step by step, focusing on key React functions and themes used to build this editor.
Prerequisites
Before we start, make sure you have a basic understanding of React, Material-UI, and Quill.js. If you’re new to these technologies, it might be helpful to review their official documentation.
Setting Up the Project
To get started, create a new React project or use an existing one. We’ll assume you have a basic React project structure in place.
Exploring the Code
Our blog editor consists of two main components: QuillEditor Toolbar and QuillEditor.
QuillEditorToolbar
The QuillEditorToolbar component handles formatting options for our editor. It provides features like selecting fonts, font sizes, headings, bold, italic, underline, and more. Let’s break down this component step by step:
- Font and Font Size Selection: Users can select a font family and font size for their text.
- Headings: Users can choose from various heading styles (Heading 1, Heading 2, etc.) to structure their content.
- Text Formatting: Options for bold, italic, underline, and strike-through text.
- Color and Background Color: Users can change the text and background color.
- List Formatting: Options for ordered and unordered lists.
- Script and Block Formatting: Features like superscript, subscript, code blocks, and blockquotes.
- Text Direction and Alignment: Options for text direction (RTL/LTR) and alignment.
- Links, Images, and Videos: Inserting hyperlinks, images, and videos into the content.
- Undo and Redo: Undo and redo functionality with corresponding icons.
- Clean Formatting: Removing formatting to get plain text.
The QuillEditorToolbar component is designed to be customizable. You can toggle features on or off by passing the isSimple prop.
Code:
import { Quill } from "react-quill";
import { Icon } from "@iconify/react";
import roundUndo from "@iconify/icons-ic/round-undo";
import roundRedo from "@iconify/icons-ic/round-redo";
import QuillEditorToolbarStyle from "./QuillEditorToolbarStyle";
const FONT_FAMILY = ["Arial", "Tahoma", "Georgia", "Impact", "Verdana"];
const FONT_SIZE = [
"8px",
"9px",
"10px",
"12px",
"14px",
"16px",
"20px",
"24px",
"32px",
"42px",
"54px",
"68px",
"84px",
"98px",
];
const HEADINGS = [
"Heading 1",
"Heading 2",
"Heading 3",
"Heading 4",
"Heading 5",
"Heading 6",
];
export function undoChange() {
this.quill.history.undo();
}
export function redoChange() {
this.quill.history.redo();
}
const Size = Quill.import("attributors/style/size");
Size.whitelist = FONT_SIZE;
Quill.register(Size, true);
const Font = Quill.import("attributors/style/font");
Font.whitelist = FONT_FAMILY;
Quill.register(Font, true);
export const formats = [
"align",
"background",
"blockquote",
"bold",
"bullet",
"code",
"code-block",
"color",
"direction",
"font",
"formula",
"header",
"image",
"indent",
"italic",
"link",
"list",
"script",
"size",
"strike",
"table",
"underline",
"video",
];
QuillEditorToolbar.propTypes = {
id: PropTypes.string.isRequired,
isSimple: PropTypes.bool,
};
export default function QuillEditorToolbar({ id, isSimple, ...other }) {
return (
<QuillEditorToolbarStyle {...other}>
<div id={id}>
<div className="ql-formats">
{!isSimple && (
<select className="ql-font" defaultValue="">
<option value="">Font</option>
{FONT_FAMILY.map((font) => (
<option key={font} value={font}>
{font}
</option>
))}
</select>
)}
{!isSimple && (
<select className="ql-size" defaultValue="16px">
{FONT_SIZE.map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</select>
)}
<select className="ql-header" defaultValue="">
{HEADINGS.map((heading, index) => (
<option key={heading} value={index + 1}>
{heading}
</option>
))}
<option value="">Normal</option>
</select>
</div>
<div className="ql-formats">
<button type="button" className="ql-bold" />
<button type="button" className="ql-italic" />
<button type="button" className="ql-underline" />
<button type="button" className="ql-strike" />
</div>
{!isSimple && (
<div className="ql-formats">
<select className="ql-color" />
<select className="ql-background" />
</div>
)}
<div className="ql-formats">
<button type="button" className="ql-list" value="ordered" />
<button type="button" className="ql-list" value="bullet" />
{!isSimple && (
<button type="button" className="ql-indent" value="-1" />
)}
{!isSimple && (
<button type="button" className="ql-indent" value="+1" />
)}
</div>
{!isSimple && (
<div className="ql-formats">
<button type="button" className="ql-script" value="super" />
<button type="button" className="ql-script" value="sub" />
</div>
)}
{!isSimple && (
<div className="ql-formats">
<button type="button" className="ql-code-block" />
<button type="button" className="ql-blockquote" />
</div>
)}
<div className="ql-formats">
<button type="button" className="ql-direction" value="rtl" />
<select className="ql-align" />
</div>
<div className="ql-formats">
<button type="button" className="ql-link" />
<button type="button" className="ql-image" />
<button type="button" className="ql-video" />
</div>
<div className="ql-formats">
{!isSimple && <button type="button" className="ql-formula" />}
<button type="button" className="ql-clean" />
</div>
{!isSimple && (
<div className="ql-formats">
<button type="button" className="ql-undo">
<Icon icon={roundUndo} width={18} height={18} />
</button>
<button type="button" className="ql-redo">
<Icon icon={roundRedo} width={18} height={18} />
</button>
</div>
)}
</div>
</QuillEditorToolbarStyle>
);
}
QuillEditorToolbarStyle.js
import PropTypes from "prop-types";
import ReactQuill from "react-quill";
import { styled } from "@material-ui/core/styles";
import EditorToolbar, {
formats,
redoChange,
undoChange,
} from "./QuillEditorToolbar";
const RootStyle = styled("div")(({ theme }) => ({
borderRadius: theme.shape.borderRadius,
border: `solid 1px ${theme.palette.grey[500_32]}`,
"& .ql-container.ql-snow": {
borderColor: "transparent",
...theme.typography.body1,
fontFamily: theme.typography.fontFamily,
},
"& .ql-editor": {
minHeight: 200,
"&.ql-blank::before": {
fontStyle: "normal",
color: theme.palette.text.disabled,
},
"& pre.ql-syntax": {
...theme.typography.body2,
padding: theme.spacing(2),
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.grey[900],
},
},
}));
QuillEditor.propTypes = {
id: PropTypes.string,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
error: PropTypes.bool,
simple: PropTypes.bool,
sx: PropTypes.object,
};
export default function QuillEditor({
id,
error,
value,
onChange,
simple = false,
sx,
...other
}) {
const modules = {
toolbar: {
container: `#${id}`,
handlers: {
undo: undoChange,
redo: redoChange,
},
},
history: {
delay: 500,
maxStack: 100,
userOnly: true,
},
syntax: true,
clipboard: {
matchVisual: false,
},
};
return (
<RootStyle
sx={{
...(error && {
border: (theme) => `solid 1px ${theme.palette.error.main}`,
}),
...sx,
}}
>
<EditorToolbar id={id} isSimple={simple} />
<ReactQuill
value={value}
onChange={onChange}
modules={modules}
formats={formats}
placeholder="Write something awesome..."
{...other}
/>
</RootStyle>
);
}
QuillEditor
The QuillEditor component is where the actual text editing takes place. It utilizes the Quill.js editor and the toolbar provided by the QuillEditorToolbar. Key features of this component include:
- Rich Text Editing: Users can write and format their blog posts using the Quill.js editor.
- Undo and Redo: The undo and redo buttons are wired up to the Quill.js history functionality.
- Error Handling: The component handles error states, such as displaying a red border when an error occurs.
- Custom Styles: The component’s styles are defined using Material-UI’s styled function, allowing for easy theming and customization.
- Modules Configuration: The modules object is passed to the Quill.js editor, configuring its behavior. This includes undo/redo functionality, syntax highlighting, and clipboard settings.
Theming and Styling
The styling of this blog editor is achieved using Material-UI. The components are styled with Material-UI’s styled function, allowing for theming and customization. You can easily adapt the editor’s appearance to match your application’s theme.
Conclusion
In this tutorial, we’ve created a blog editor using React, Material-UI, and Quill.js. We explored the QuillEditorToolbar and QuillEditor components, focusing on their functionality and customization options. With this blog editor, you can empower your users to create beautifully formatted blog posts with ease.