Framework Integration
How to use Marzipan with popular JavaScript frameworks.
React
Basic Integration
tsx
import { useEffect, useRef } from 'react';
import { Marzipan } from '@pinkpixel/marzipan';
export function MarkdownEditor() {
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<Marzipan | null>(null);
useEffect(() => {
if (!containerRef.current) return;
const [editor] = new Marzipan(containerRef.current, {
toolbar: true,
placeholder: 'Start typing...',
});
editorRef.current = editor;
return () => {
editor.destroy?.();
};
}, []);
return <div ref={containerRef} />;
}
Controlled Component
tsx
import { useEffect, useRef, useState } from 'react';
import { Marzipan } from '@pinkpixel/marzipan';
export function ControlledEditor({ value, onChange }) {
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<Marzipan | null>(null);
useEffect(() => {
if (!containerRef.current) return;
const [editor] = new Marzipan(containerRef.current, {
value,
onChange: (newValue) => {
onChange(newValue);
},
});
editorRef.current = editor;
return () => editor.destroy?.();
}, []);
// Sync external value changes
useEffect(() => {
if (editorRef.current && value !== editorRef.current.getValue()) {
editorRef.current.setValue(value);
}
}, [value]);
return <div ref={containerRef} />;
}
Vue 3
Composition API
vue
<template>
<div ref="editorRef" />
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { Marzipan } from '@pinkpixel/marzipan';
const editorRef = ref(null);
let editor = null;
onMounted(() => {
if (editorRef.value) {
[editor] = new Marzipan(editorRef.value, {
toolbar: true,
placeholder: 'Start typing...',
});
}
});
onBeforeUnmount(() => {
editor?.destroy();
});
</script>
With v-model
vue
<template>
<div ref="editorRef" />
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
import { Marzipan } from '@pinkpixel/marzipan';
const props = defineProps({
modelValue: String
});
const emit = defineEmits(['update:modelValue']);
const editorRef = ref(null);
let editor = null;
onMounted(() => {
if (editorRef.value) {
[editor] = new Marzipan(editorRef.value, {
value: props.modelValue,
onChange: (value) => {
emit('update:modelValue', value);
},
});
}
});
watch(() => props.modelValue, (newValue) => {
if (editor && newValue !== editor.getValue()) {
editor.setValue(newValue);
}
});
onBeforeUnmount(() => {
editor?.destroy();
});
</script>
Svelte
Basic Component
svelte
<script>
import { onMount, onDestroy } from 'svelte';
import { Marzipan } from '@pinkpixel/marzipan';
let container;
let editor;
onMount(() => {
[editor] = new Marzipan(container, {
toolbar: true,
placeholder: 'Start typing...',
});
});
onDestroy(() => {
editor?.destroy();
});
</script>
<div bind:this={container} />
With Binding
svelte
<script>
import { onMount, onDestroy } from 'svelte';
import { Marzipan } from '@pinkpixel/marzipan';
export let value = '';
let container;
let editor;
let updating = false;
onMount(() => {
[editor] = new Marzipan(container, {
value,
onChange: (newValue) => {
updating = true;
value = newValue;
updating = false;
},
});
});
$: if (editor && !updating && value !== editor.getValue()) {
editor.setValue(value);
}
onDestroy(() => {
editor?.destroy();
});
</script>
<div bind:this={container} />
Angular
Component
typescript
import { Component, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
import { Marzipan } from '@pinkpixel/marzipan';
@Component({
selector: 'app-markdown-editor',
template: '<div #editorContainer></div>',
})
export class MarkdownEditorComponent implements OnInit, OnDestroy {
@ViewChild('editorContainer') containerRef!: ElementRef;
private editor?: Marzipan;
ngOnInit() {
if (this.containerRef?.nativeElement) {
const [editor] = new Marzipan(this.containerRef.nativeElement, {
toolbar: true,
placeholder: 'Start typing...',
});
this.editor = editor;
}
}
ngOnDestroy() {
this.editor?.destroy();
}
}
Vanilla JavaScript
ES Modules
js
import { Marzipan } from '@pinkpixel/marzipan';
const [editor] = new Marzipan('#editor', {
toolbar: true,
onChange: (value) => {
console.log('Content:', value);
}
});
Multiple Instances
js
const editors = new Marzipan('.markdown-editor', {
toolbar: true,
showStats: true,
});
// Access individual editors
editors.forEach((editor, index) => {
console.log(`Editor ${index}:`, editor.getValue());
});
Next.js
App Router
tsx
'use client';
import { useEffect, useRef } from 'react';
import { Marzipan } from '@pinkpixel/marzipan';
export default function Editor() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!ref.current) return;
const [editor] = new Marzipan(ref.current, {
toolbar: true,
});
return () => editor.destroy?.();
}, []);
return <div ref={ref} />;
}
Best Practices
- Always clean up - Call
destroy()when unmounting - Single initialization - Create editor only once per mount
- Sync carefully - Avoid infinite loops when syncing external state
- Use refs - Don't recreate editors on every render
- Handle SSR - Guard against
windowaccess in SSR frameworks
Common Patterns
Auto-save
ts
let saveTimeout;
const [editor] = new Marzipan('#editor', {
onChange: (value) => {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
localStorage.setItem('draft', value);
}, 1000);
}
});
Export HTML
ts
const exportButton = document.querySelector('#export');
exportButton.addEventListener('click', () => {
const html = editor.getCleanHTML();
downloadFile('content.html', html);
});
For more examples, check out the Bakeshop Demo.
Marzipan