In today’s post, we’ll dive deep into integrating a popular React Markdown editor, react-simplemde-editor
, with a custom backend for image uploads.
Introduction
The react-simplemde-editor
provides a user-friendly interface for content creation in the Markdown format. By default, it supports a variety of features, but one aspect we’d like to customize is the image upload functionality. We want to leverage our own backend to store and serve the images.
The Component: MarkdownEditor
Let’s start by examining the provided React component, MarkdownEditor
:
import React, { useRef, useCallback } from "react";
import EasyMDE from "easymde";
import "easymde/dist/easymde.min.css";
import ReactDOMServer from "react-dom/server";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { SimpleMdeReact } from "react-simplemde-editor";
Here, we import the necessary packages, including the editor itself, markdown rendering utilities, and styles.
Props and States
The MarkdownEditor
component accepts two props: value
and onChange
.
interface MarkdownEditorProps {
value: string;
onChange: (value: string) => void;
}
value
: Represents the current Markdown content.onChange
: A callback to inform parent components about content changes.
Editor Configuration
Next, the component sets up the editor configuration:
const releaseNote = useRef<string>("");
This useRef
is utilized to store the current content, enabling efficient updates without unnecessary re-renders.
The primary configuration object for the editor is options
. Within it, we define:
- The markdown formatting styles for different elements.
- The toolbar items.
- A custom image upload function that interfaces with our backend.
- Other aesthetic configurations.
Custom Image Upload
A key part of this setup is the imageUploadFunction
:
imageUploadFunction(file, onSuccess, onError) {
const formData = new FormData();
formData.append("data", file);
fetch("/...yoururl.../imageupload", {
method: "POST",
body: formData,
})
.then(response => response.json())
.then(data => {
if (data.url) {
onSuccess(data.url);
} else {
onError("Error: No URL returned from server.");
}
})
.catch(error => {
onError("Error uploading image: " + error.toString());
});
},
Whenever a user tries to upload an image, this function is triggered. It sends the image to our backend and then either succeeds (providing the URL of the uploaded image) or fails (with an error message).
Rendering the Preview
Another interesting feature is the custom preview rendering. Instead of relying on the default markdown rendering, we leverage ReactDOMServer
and ReactMarkdown
for enhanced rendering capabilities.
previewRender: () =>
ReactDOMServer.renderToString(
<ReactMarkdown className="table-auto" remarkPlugins={[remarkGfm]}>
{releaseNote.current}
</ReactMarkdown>
),
With this, our Markdown content supports GitHub-flavored markdown, thanks to remarkGfm
.
Wrapping Up the Component
Lastly, the component returns the actual editor:
return (
<SimpleMdeReact
value={value}
onChange={handleContentChange}
options={options}
/>
);
The handleContentChange
function simply updates our releaseNote
ref and calls the passed-in onChange
function.
Complete Code
import React, { useRef, useCallback } from "react";
import EasyMDE from "easymde";
import "easymde/dist/easymde.min.css";
import ReactDOMServer from "react-dom/server";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { SimpleMdeReact } from "react-simplemde-editor";
interface MarkdownEditorProps {
value: string;
onChange: (value: string) => void;
}
export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
value,
onChange,
}) => {
const releaseNote = useRef<string>("");
const options = {
blockStyles: {
code: "```",
},
toolbar: [
"bold",
"italic",
"heading",
"heading-1",
"heading-2",
"code",
"quote",
"unordered-list",
"ordered-list",
"link",
"upload-image",
"table",
"horizontal-rule",
"preview",
"guide",
"redo",
"undo",
"side-by-side",
],
imageUploadFunction(file, onSuccess, onError) {
const formData = new FormData();
formData.append("data", file);
fetch("/...yoururl.../imageupload", {
method: "POST",
body: formData,
})
.then((response) => response.json())
.then((data) => {
if (data.url) {
onSuccess(data.url);
} else {
onError("Error: No URL returned from server.");
}
})
.catch((error) => {
onError("Error uploading image: " + error.toString());
});
},
minHeight: "49vh",
sideBySideFullscreen: false,
previewRender: () =>
ReactDOMServer.renderToString(
<ReactMarkdown className="table-auto" remarkPlugins={[remarkGfm]}>
{releaseNote.current}
</ReactMarkdown>
),
previewClass: "p-[20px]",
borderColor: "rgba(255, 255, 255",
} as EasyMDE.Options;
const handleContentChange = useCallback(
(value: string) => {
releaseNote.current = value;
onChange(value);
},
[onChange]
);
return (
<SimpleMdeReact
value={value}
onChange={handleContentChange}
options={options}
/>
);
};