React Markdown Editor Component
react-mde is a React component that provides a feature-rich Markdown editor, aiming for parity with the GitHub Markdown editor experience. It functions as a controlled component, requiring both a `value` prop for the Markdown content and an `onChange` handler for updates. A key differentiator is its extensibility; it allows developers to provide a custom `generateMarkdownPreview` function, making it agnostic to the chosen Markdown parsing library (e.g., Showdown or react-markdown). The component also supports custom icons and configurable toolbar commands. It relies on Draft.js for advanced text editing features like history management and mentions. The current stable version is 11.5.0, published approximately five years ago. Due to the lack of recent updates and noted unresolved issues, such as mobile compatibility tied to an unaddressed Draft.js problem, the project appears to be unmaintained.
Common errors
-
Cannot find module 'react-mde' or its corresponding type declarations.
cause The `react-mde` package is not installed or incorrectly imported.fixRun `npm install react-mde showdown` (or `yarn add react-mde showdown`). Ensure the import path is `import ReactMde from 'react-mde';`. -
TypeError: Cannot read properties of undefined (reading 'makeHtml') or similar error related to markdown conversion.
cause The `generateMarkdownPreview` prop is either missing, returns an invalid value, or the Markdown converter (e.g., Showdown) is not correctly initialized or installed.fixEnsure `showdown` is installed (`npm install showdown`) and correctly imported. The `generateMarkdownPreview` function must return a `Promise<string | ReactElement>`. Example: `generateMarkdownPreview={markdown => Promise.resolve(converter.makeHtml(markdown))}`. -
Error: A component is changing an uncontrolled input of type text to be controlled.
cause The `ReactMde` component is being used with a `value` prop that changes from `undefined` to a defined string, or vice-versa, without a corresponding `key` prop to force re-mounting.fixAlways initialize the `value` prop with a non-null/non-undefined string, typically an empty string (`''`) or initial Markdown content. Ensure `value` and `onChange` are consistently provided for controlled component behavior.
Warnings
- breaking The project appears to be unmaintained. The latest version (11.5.0) was published approximately five years ago, indicating a lack of ongoing support, bug fixes, or compatibility updates for newer React versions or browser changes.
- gotcha React-mde does not automatically sanitize the HTML output generated for the Markdown preview. Using client-side Markdown parsers like Showdown without explicit sanitization can expose your application to Cross-Site Scripting (XSS) vulnerabilities if user-supplied content contains malicious scripts.
- gotcha Mobile support for react-mde is noted as problematic and depends on fixes for Draft.js, which are not being actively addressed by Draft.js maintainers (Facebook). This means the editor may not function correctly or provide a good user experience on mobile devices.
- gotcha Styling requires importing a CSS file. If `react-mde/lib/styles/css/react-mde-all.css` is not imported, the editor will appear unstyled and potentially non-functional visually.
Install
-
npm install react-mde -
yarn add react-mde -
pnpm add react-mde
Imports
- ReactMde
import { ReactMde } from 'react-mde'; import * as ReactMde from 'react-mde';import ReactMde from 'react-mde';
- CSS Styles
import 'react-mde/lib/styles/css/react-mde.css';
import 'react-mde/lib/styles/css/react-mde-all.css';
- Showdown Converter (example)
import Showdown from 'showdown';
import * as Showdown from 'showdown'; const converter = new Showdown.Converter({ tables: true, simplifiedAutoLink: true });
Quickstart
import React, { useState, useCallback } from 'react';
import ReactMde from 'react-mde';
import * as Showdown from 'showdown';
import 'react-mde/lib/styles/css/react-mde-all.css';
const converter = new Showdown.Converter({
tables: true,
simplifiedAutoLink: true,
strikethrough: true,
tasklists: true,
});
function MyMarkdownEditor() {
const [value, setValue] = useState('**Hello world!!!**');
const [selectedTab, setSelectedTab] = useState<'write' | 'preview'>('write');
const generatePreview = useCallback((markdown) => {
return Promise.resolve(converter.makeHtml(markdown));
}, []);
return (
<div style={{ maxWidth: 800, margin: 'auto', padding: '20px' }}>
<ReactMde
value={value}
onChange={setValue}
selectedTab={selectedTab}
onTabChange={setSelectedTab}
generateMarkdownPreview={generatePreview}
minEditorHeight={200}
minPreviewHeight={200}
/>
</div>
);
}
export default MyMarkdownEditor;