React integration

A rich text editor for React

yjd is a dependency-free, tree-shakeable rich text editor. It has no framework baked in, so you wrap it in about 15 lines of a hook — controlled valueonChange, StrictMode-safe, with TypeScript types out of the box. Ship ~17 KB, not a whole editor framework.

01 — Install

Add the package

One dependency, no peer requirements. The stylesheet is a single import.

terminal
npm i @oix1987/yjd
02 — The wrapper

A reusable hook component

Create the editor once in an effect and destroy() it on cleanup — that cleanup is what makes it safe under React 18 StrictMode (which mounts effects twice in dev). Sync incoming value only when it differs, so typing never resets the caret.

Editor.jsx
import { useEffect, useRef } from 'react';
import yjd from '@oix1987/yjd';
import '@oix1987/yjd/styles.css';   // once in your app

export function Editor({ value, onChange, placeholder }) {
  const host = useRef(null);
  const ed = useRef(null);

  useEffect(() => {
    ed.current = new yjd(host.current, {
      content: value ?? '',
      placeholder,
      onChange: (html) => onChange?.(html),
    });
    return () => ed.current.destroy();      // StrictMode-safe
  }, []);

  // controlled: set only when different (no caret jump)
  useEffect(() => {
    const e = ed.current;
    if (e && value != null && value !== e.getContent()) e.setContent(value);
  }, [value]);

  return <div ref={host} />;
}
03 — Use it
App.jsx
const [html, setHtml] = useState('<p>Hello</p>');
<Editor value={html} onChange={setHtml} placeholder="Write…" />
Live

The editor itself

This is a real yjd instance (the same one the hook mounts). Type in it. Want it wrapped in actual React? Open the running React example.

Editor.jsx — live

Why wrap yjd instead of a React-only editor

~17 KB, tree-shakeable

Register only the formats you use from the /core entry. No editor framework in the bundle.

TypeScript types

Ships index.d.ts — props and options are typed with no extra install.

StrictMode-safe

The destroy() cleanup handles React 18's double-mount cleanly — zero leaks.

No lock-in

The same core runs in Vue, Svelte and vanilla — one editor across your stack.

💡 SSR (Next.js): the editor touches the DOM, so it must mount on the client. Inside useEffect it already does; for a whole page use dynamic(() => import('./Editor'), { ssr: false }).

Next

Running React example · Using Vue instead? · Playground presets · Full docs