Examples › Track 4 — Integrations › Example 14
React integration
quikdown is framework-agnostic. Both the parser and the editor work inside React with no special wrapper. Here are the two patterns you'll need.
Pattern 1: Parser only — render markdown as HTML
Use quikdown to convert markdown to HTML, then drop it into a React element with dangerouslySetInnerHTML. quikdown is XSS-safe by default, so this is safe with trusted or LLM-generated input.
import { useMemo } from 'react';
import quikdown from 'quikdown';
export function Markdown({ source }) {
const html = useMemo(() => quikdown(source), [source]);
return <div className="markdown" dangerouslySetInnerHTML={{ __html: html }} />;
}
// usage
<Markdown source="# Hello\n\n**world**" />
Pattern 2: Full editor — wrap QuikdownEditor in a component
The editor manages its own DOM, so you mount it in useEffect after the component renders. Pass content through props and call editor.setMarkdown() when the prop changes.
import { useEffect, useRef } from 'react';
import QuikdownEditor from 'quikdown/edit';
export function MarkdownEditor({
value,
onChange,
mode = 'split',
theme = 'auto'
}) {
const containerRef = useRef(null);
const editorRef = useRef(null);
// Mount the editor once
useEffect(() => {
const editor = new QuikdownEditor(containerRef.current, {
mode,
theme,
showUndoRedo: true,
onChange: (md, html) => onChange?.(md, html),
});
editor.setMarkdown(value || '');
editorRef.current = editor;
return () => editor.destroy();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Sync external value changes back into the editor
useEffect(() => {
if (editorRef.current && value !== editorRef.current.getMarkdown()) {
editorRef.current.setMarkdown(value || '');
}
}, [value]);
// React to mode/theme prop changes
useEffect(() => { editorRef.current?.setMode(mode); }, [mode]);
useEffect(() => { editorRef.current?.setTheme(theme); }, [theme]);
return <div ref={containerRef} style={{ height: 500 }} />;
}
// usage
function App() {
const [md, setMd] = useState('# Hello\n\nReact + quikdown');
return <MarkdownEditor value={md} onChange={setMd} theme="auto" />;
}
Things to know
- Mount once. The editor manages its own contenteditable DOM. Don't recreate it on every render — that loses focus and edit history.
- One-way sync from props. Compare incoming
valueagainsteditor.getMarkdown()before callingsetMarkdown, otherwise you'll churn the editor on every keystroke. - Cleanup. Call
editor.destroy()in the effect's cleanup function so contenteditable listeners and DOM nodes don't leak. - SSR. Both
quikdown(parser) andquikdown/bdwork in Node — they have no DOM dependency. The fullquikdown/editcomponent requires DOM and should be loaded with dynamic import in Next.js:const QuikdownEditor = dynamic(() => import('quikdown/edit'), { ssr: false });