Mount the studio after React creates its host element, keep document state in your application, and destroy the instance on unmount.
npm install github:aneebji/html-editor#v1.1.0import { useEffect, useRef } from "react";
import { mount, type StudioHandle } from "html-device-preview";
type Props = {
html: string;
filename?: string;
onChange: (html: string) => void;
};
export function ViewportLab({
html,
filename = "Untitled.html",
onChange,
}: Props) {
const host = useRef<HTMLDivElement>(null);
const studio = useRef<StudioHandle | null>(null);
const onChangeRef = useRef(onChange);
useEffect(() => {
onChangeRef.current = onChange;
}, [onChange]);
useEffect(() => {
if (!host.current) return;
const instance = mount(host.current, {
height: "820px",
onChange: (source) => onChangeRef.current(source),
});
studio.current = instance;
return () => {
instance.destroy();
studio.current = null;
};
}, []);
useEffect(() => {
if (studio.current?.getHtml() !== html) studio.current?.setHtml(html);
}, [html]);
useEffect(() => {
studio.current?.setFilename(filename);
}, [filename]);
return <div ref={host} style={{ height: 820, minHeight: 640 }} />;
}import { useState } from "react";
import { ViewportLab } from "./ViewportLab";
export function PageEditor() {
const [html, setHtml] = useState("");
return <ViewportLab html={html} onChange={setHtml} filename="page.html" />;
}Submit html through your application's normal save flow. onChange receives user document changes immediately; setHtml() is silent, so synchronizing incoming props does not create a callback loop. Use setOptions() for workspace changes instead of remounting.
The library requires a browser environment. For server-rendered applications, follow the Next.js guide.