- Created a new document for using components in Milkdown. - Added a guide for using plugins in Milkdown, including toggling plugins programmatically and listing official plugins. - Introduced a recipe for integrating Milkdown with Angular, including installation steps and component creation. - Added a recipe for using Milkdown with Next.js, detailing installation and component setup. - Created a guide for integrating Milkdown with NuxtJS, including installation and component creation. - Added a comprehensive guide for using Milkdown with React, covering both Crepe and core Milkdown usage. - Introduced a recipe for SolidJS integration with Milkdown, including installation and component creation. - Added a guide for using Milkdown with Svelte, detailing installation and component setup. - Created a comprehensive guide for integrating Milkdown with Vue, covering both Crepe and core Milkdown usage. - Added a recipe for using Milkdown with Vue2, including installation and component creation.
5.0 KiB
React Integration
Milkdown provides first-class React support with dedicated packages and hooks for seamless integration. You can choose between Crepe, our feature-rich WYSIWYG editor, or the core Milkdown editor for more customization options.
Using Crepe
Crepe is a powerful, feature-rich Markdown editor built on top of Milkdown that provides a more user-friendly editing experience.
Installation
npm install @milkdown/crepe @milkdown/react @milkdown/kit
Implementation
import { Crepe } from "@milkdown/crepe";
import { Milkdown, MilkdownProvider, useEditor } from "@milkdown/react";
const CrepeEditor: React.FC = () => {
const { get } = useEditor((root) => {
return new Crepe({ root });
});
return <Milkdown />;
};
export const MilkdownEditorWrapper: React.FC = () => {
return (
<MilkdownProvider>
<CrepeEditor />
</MilkdownProvider>
);
};
Online Demo
::iframe{src="https://stackblitz.com/github/Milkdown/examples/tree/main/react-crepe"}
Using Milkdown
For more advanced use cases or when you need full control over the editor's configuration, you can use the core Milkdown editor directly.
Install Dependencies
npm install @milkdown/react @milkdown/kit
Basic Usage
Here's a minimal example to get started:
import { Editor, rootCtx } from "@milkdown/kit/core";
import { commonmark } from "@milkdown/kit/preset/commonmark";
import { Milkdown, MilkdownProvider, useEditor } from "@milkdown/react";
import { nord } from "@milkdown/theme-nord";
const MilkdownEditor: React.FC = () => {
const { get } = useEditor((root) =>
Editor.make()
.config(nord)
.config((ctx) => {
ctx.set(rootCtx, root);
})
.use(commonmark),
);
return <Milkdown />;
};
export const MilkdownEditorWrapper: React.FC = () => {
return (
<MilkdownProvider>
<MilkdownEditor />
</MilkdownProvider>
);
};
::iframe{src="https://stackblitz.com/github/Milkdown/examples/tree/main/react-commonmark"}
Advanced Usage
Accessing Editor Instance
The useInstance() hook can only be used within components that are children of MilkdownProvider. It returns a tuple containing a loading state and a getter function to access the editor instance.
import { useInstance } from "@milkdown/react";
import { getMarkdown } from "@milkdown/utils";
// ❌ This won't work - ParentComponent is outside MilkdownProvider
const ParentComponent: React.FC = () => {
const [isLoading, getInstance] = useInstance(); // This will be [true, () => undefined]
return <MilkdownEditorWrapper />;
};
// ✅ This is the correct way - EditorControls is inside MilkdownProvider
const EditorControls: React.FC = () => {
const [isLoading, getInstance] = useInstance();
const handleSave = () => {
if (isLoading) return;
const editor = getInstance();
if (!editor) return;
const content = editor.action(getMarkdown());
// Do something with the content
};
return (
<button onClick={handleSave} disabled={isLoading}>
Save
</button>
);
};
// ✅ Proper component structure
const EditorWithControls: React.FC = () => {
return (
<MilkdownProvider>
<MilkdownEditorWrapper />
<EditorControls />
</MilkdownProvider>
);
};
Best Practices
-
Component Structure
- Keep the editor component separate from business logic
- Wrap the editor with
MilkdownProviderat the highest necessary level - Use TypeScript for better type safety
-
Performance
- Memoize the editor configuration if it's complex
- Use React.memo for the editor component if needed
- Avoid unnecessary re-renders of the editor
Common Use Cases
Form Integration
const FormWithEditor: React.FC = () => {
const [isLoading, getInstance] = useInstance();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (isLoading) return;
const editor = getInstance();
if (!editor) return;
const content = editor.action(getMarkdown());
// Submit form with content
};
return (
<form onSubmit={handleSubmit}>
<MilkdownEditorWrapper />
<button type="submit" disabled={isLoading}>
Submit
</button>
</form>
);
};
Auto-save
import { Editor, rootCtx } from "@milkdown/kit/core";
import { commonmark } from "@milkdown/kit/preset/commonmark";
import { listener, listenerCtx } from "@milkdown/kit/plugin/listener";
import { Milkdown, useEditor } from "@milkdown/react";
const AutoSaveEditor: React.FC = () => {
const { get } = useEditor((root) =>
Editor.make()
.config((ctx) => {
ctx.set(rootCtx, root);
// Add markdown listener for auto-save
ctx.get(listenerCtx).markdownUpdated((ctx, markdown) => {
// Save content to your backend or storage
saveToBackend(markdown);
});
})
.use(commonmark)
.use(listener),
);
return <Milkdown />;
};