---
title: React (Vite)
description: Eine lauffähige Single-Page-App mit Vite, React 19 und dem Templatical-Editor, ohne Backend, als Ausgangspunkt für ein React-Frontend auf jedem Server.
---

# React (Vite)

[`examples/react-vite`](https://github.com/templatical/sdk/tree/main/examples/react-vite) ist eine Single-Page-App mit Vite und React 19, die den Editor enthält und kein Backend hat. Sie hält das Template in `localStorage`, speichert gespeicherte Blöcke mit dem `createLocalStorageSavedBlocksProvider()` des Editors und rendert MJML im Browser mit `@templatical/renderer`. Sie dient als Ausgangspunkt für ein React-Frontend, dessen Backend nicht JavaScript ist. CI baut die App und führt sie bei jeder Änderung am SDK in einem Browser aus.

## Ausführen des Beispiels

[In StackBlitz öffnen](https://stackblitz.com/github/templatical/sdk/tree/main/examples/react-vite)

Oder kopieren Sie es in ein neues Verzeichnis:

```bash
npx degit templatical/sdk/examples/react-vite my-app
cd my-app
npm install
npm run dev
```

## Die Editor-Komponente

Die Komponente bindet den Editor in einem Effect ein, und ihr Cleanup unmountet ihn – auch einen Editor, der erst fertig lädt, nachdem React StrictMode den Effect aufgeräumt hat. `onChange` schreibt jede Änderung nach `localStorage`, und der nächste Mount übergibt sie als `content` zurück an den Editor. Startet der Editor nicht, zeigt die Werkzeugleiste die Fehlermeldung an. Die Werkzeugleiste zeigt auch Fehler, die der Editor über `onError` meldet, etwa gespeicherte Blöcke, die er nicht aus `localStorage` lesen kann, und einen fehlgeschlagenen Export; der nächste Export entfernt sie.

`src/email-editor.tsx`

```tsx
import { useEffect, useRef, useState } from "react";
import {
  createLocalStorageSavedBlocksProvider,
  init,
  type TemplaticalEditor,
} from "@templatical/editor";
import type { TemplateContent } from "@templatical/types";
import "@templatical/editor/style.css";

// Everything here runs in the browser: the template is kept in localStorage,
// and saved blocks use the editor's own localStorage provider. To store them
// on a server instead, use the fetch-based providers from any full-stack
// example (lib/templatical/providers.ts in examples/nextjs).
const CONTENT_KEY = "templatical-example:content";

function storedContent(): TemplateContent | undefined {
  try {
    const raw = localStorage.getItem(CONTENT_KEY);
    return raw ? (JSON.parse(raw) as TemplateContent) : undefined;
  } catch {
    return undefined;
  }
}

const messageOf = (error: unknown) =>
  error instanceof Error ? error.message : String(error);

export function EmailEditor() {
  const containerRef = useRef<HTMLDivElement>(null);
  const editorRef = useRef<TemplaticalEditor | null>(null);
  const [mjml, setMjml] = useState("");
  const [problem, setProblem] = useState<string | null>(null);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    let cancelled = false;
    let instance: TemplaticalEditor | null = null;

    (async () => {
      // In development, React StrictMode runs this effect, its cleanup and this
      // effect again in one synchronous pass. Two init() calls on one
      // container can finish in either order, and each replaces whatever the
      // container holds when it finishes, so a cancelled run that finishes
      // last leaves the container empty. Yielding once lets that cleanup
      // cancel the first run before it calls init().
      await Promise.resolve();
      if (cancelled) return;
      const ed = await init({
        container,
        content: storedContent(),
        onChange(content) {
          localStorage.setItem(CONTENT_KEY, JSON.stringify(content));
        },
        savedBlocks: createLocalStorageSavedBlocksProvider(),
        onError: (error) => {
          if (!cancelled) setProblem(error.message);
        },
      });
      if (cancelled) {
        ed.unmount();
        return;
      }
      instance = ed;
      editorRef.current = ed;
    })().catch((error: unknown) => {
      if (!cancelled) setProblem(messageOf(error));
    });

    return () => {
      cancelled = true;
      instance?.unmount();
      editorRef.current = null;
    };
  }, []);

  async function exportMjml() {
    const ed = editorRef.current;
    // No editor yet, or it failed to start: nothing to export, and a start
    // failure keeps its alert.
    if (!ed) return;
    // A new export replaces an earlier failure.
    setProblem(null);
    try {
      setMjml(await ed.toMjml());
    } catch (error) {
      setProblem(messageOf(error));
    }
  }

  return (
    <>
      <div className="toolbar">
        <button type="button" data-testid="export-mjml" onClick={exportMjml}>
          Export MJML
        </button>
        {problem && <span role="alert">{problem}</span>}
      </div>
      <div className="workspace">
        <div ref={containerRef} className="editor" />
        {mjml && (
          <pre data-testid="export-output" className="output">
            {mjml}
          </pre>
        )}
      </div>
    </>
  );
}
```

## Die Provider {#providers}

Dieses Beispiel übergibt `savedBlocks`, erzeugt mit `createLocalStorageSavedBlocksProvider()`. Um Templates und gespeicherte Blöcke auf einem Server zu halten, übergeben Sie die `fetch`-basierten Provider aus dem [Next.js-Beispiel](/de/frameworks/nextjs#providers) und implementieren Sie die Routen, die sie aufrufen, in einer beliebigen Sprache.

## Wechsel in die Produktion

- Übergeben Sie einen `templates`-Provider, damit Templates auf Ihrem Server liegen statt im `localStorage` eines einzelnen Browsers.
- Ersetzen Sie `createLocalStorageSavedBlocksProvider()` durch einen Provider, der auf Ihrem Server aufsetzt, damit gespeicherte Blöcke dem Benutzer über Browser hinweg folgen.
- Kompilieren Sie die Ausgabe von `editor.toMjml()` auf Ihrem Server mit dem Paket `mjml` zu HTML, oder übergeben Sie einen `render`-Provider, damit `editor.toHtml()` Ihren Server aufruft. [Rendering & Export](/de/backend/render) behandelt beides.
