⚛️ ReactQuick Start

Quick Start

Get a Canvus workspace running in your React application in under 5 minutes.

1. Create the Workspace

The <Canvus /> component is your canvas workspace. It handles all the internal plumbing — Shadow DOM, canvas overlay, event binding, and cleanup.

import { Canvus } from "@canvus/react";
 
function App() {
  return (
    <Canvus
      config={{ snapThreshold: 8 }}
      style={{ width: "100vw", height: "100vh" }}
    >
      <Toolbar />
    </Canvus>
  );
}

Note: Any components rendered as children of <Canvus /> can access the workspace via the useCanvus() hook.

2. Define a Component

The component you pass to addReactNode is a standard React component. It renders inside the canvas node’s frame:

interface DashboardCardProps {
  title: string;
  value: string;
}
 
function DashboardCard({ title, value }: DashboardCardProps) {
  return (
    <div
      style={{
        padding: 24,
        background: "#1e293b",
        borderRadius: 12,
        color: "#e2e8f0",
        height: "100%",
      }}
    >
      <h3>{title}</h3>
      <p style={{ fontSize: 32, fontWeight: 700 }}>{value}</p>
    </div>
  );
}

The component renders inside a wrapper frame on the canvas. The frame handles positioning and sizing — your component fills the available space.

3. Add React Nodes to the Canvas

Use the useCanvus() hook from any child component to mount live React components on the canvas:

import { useCanvus } from "@canvus/react";
 
function Toolbar() {
  const { addReactNode } = useCanvus();
 
  const handleAdd = () => {
    addReactNode({
      id: "revenue-card",
      component: DashboardCard,
      props: { title: "Revenue", value: "$42,000" },
      currentRect: { x: 100, y: 100, width: 300, height: 200 },
    });
  };
 
  return <button onClick={handleAdd}>Add Card</button>;
}

4. Update Props

To update a React node’s props (and trigger a re-render), use updateReactNode:

const { updateReactNode } = useCanvus();
 
// Update the card with new data
updateReactNode("revenue-card", {
  value: "$58,000",
});

The component re-renders in-place with merged props. No remount, no flicker.

5. Handle Commits

When a user drags or resizes a React node, the onReactNodeCommit callback fires with structured data instead of raw HTML:

<Canvus
  onReactNodeCommit={(id, snapshot) => {
    console.log(snapshot.componentName); // "DashboardCard"
    console.log(snapshot.props); // { title: "Revenue", value: "$42,000" }
    console.log(snapshot.rect); // { x: 150, y: 120, width: 300, height: 200 }
 
    // Save to your state management
    saveNodeState(id, snapshot);
  }}
  onHTMLCommit={(id, html) => {
    // Only fires for vanilla HTML nodes — React nodes are filtered out
    console.log("HTML commit:", id, html);
  }}
>
  {/* ... */}
</Canvus>

6. Mix React and HTML Nodes

React nodes and vanilla HTML nodes coexist on the same canvas:

const { addReactNode, addNode } = useCanvus();
 
// Add a React component node
addReactNode({
  id: "revenue-card",
  component: DashboardCard,
  props: { title: "Revenue", value: "$42,000" },
  currentRect: { x: 100, y: 100, width: 300, height: 200 },
});
 
// Add a vanilla HTML node (uses core API directly)
addNode({
  id: "heading-1",
  rawMarkup: "<h1>Dashboard</h1>",
  currentRect: { x: 100, y: 320, width: 300, height: 60 },
});

7. Remove Nodes

const { removeReactNode, workspace } = useCanvus();
 
// Remove a React node (unmounts the component and removes from canvas)
removeReactNode("revenue-card");
 
// Remove an HTML node (uses core API)
workspace?.removeNode("heading-1");

Full Example

import { Canvus, useCanvus } from "@canvus/react";
 
function App() {
  return (
    <Canvus
      config={{ snapThreshold: 8 }}
      onReactNodeCommit={(id, snapshot) => {
        console.log(`${snapshot.componentName} committed:`, snapshot.props, snapshot.rect);
      }}
      style={{ width: "100vw", height: "100vh" }}
    >
      <Toolbar />
    </Canvus>
  );
}
 
function Toolbar() {
  const { addReactNode, updateReactNode, removeReactNode } = useCanvus();
 
  return (
    <div style={{ position: "absolute", top: 16, left: 16, zIndex: 100 }}>
      <button
        onClick={() => {
          addReactNode({
            id: "revenue-card",
            component: DashboardCard,
            props: { title: "Revenue", value: "$42,000" },
            currentRect: { x: 100, y: 100, width: 300, height: 200 },
          });
        }}
      >
        Add Card
      </button>
      <button onClick={() => updateReactNode("revenue-card", { value: "$58,000" })}>
        Update Value
      </button>
      <button onClick={() => removeReactNode("revenue-card")}>Remove Card</button>
    </div>
  );
}
 
interface DashboardCardProps {
  title: string;
  value: string;
}
 
function DashboardCard({ title, value }: DashboardCardProps) {
  return (
    <div
      style={{
        padding: 24,
        background: "#1e293b",
        borderRadius: 12,
        color: "#e2e8f0",
        height: "100%",
      }}
    >
      <h3>{title}</h3>
      <p style={{ fontSize: 32, fontWeight: 700 }}>{value}</p>
    </div>
  );
}