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 value against editor.getMarkdown() before calling setMarkdown, 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) and quikdown/bd work in Node — they have no DOM dependency. The full quikdown/edit component requires DOM and should be loaded with dynamic import in Next.js: const QuikdownEditor = dynamic(() => import('quikdown/edit'), { ssr: false });