I am creating an app where users can edit SVGs. I have a library which is responsible for computing UI position for a very specific scientific diagram, which interacts with a React frontend. This library (which uses SVG.js) draws SVGElement's directly to the DOM and uses no React.
I want the user to be able to select an SVGElement and have it become resizable and draggable (I'm using React-rnd). The way I'm trying to do that is to:
interface IResizer { element: SVGElement }
const Resizer: React.FC<IResizer> = (props) => {
return (
<>
<Rnd ...>
<div className="content" dangerouslySetInnerHTML={{__html: props.element.outerHTML}}></div>
</Rnd>
</>
)
}
const Canvas: React.FC<ICanvasProps> = (props) => {
const [selectedElements, setSelectedElements] = useState<SVGElement[]>([]);
function canvasClicked(click: React.MouseEvent<HTMLDivElement>) {
// Get the SVGElement from the DOM.
setSelectedElements([...selectedElements, node])
// Delete the SVG from the DOM, or create the "Resizer" here and use it to replace the SVGElement?
}
return (
<>
<div id={DESTINATIONVCANVASID} onClick={(e) => canvasClicked(e)}>
</div>
{
selectedElements.map((e) => {
return (
<Resizer element={e}>
</Resizer>
)
})
}
</>
)
}
One option is you can pass around the SVG elements as React components. Keep a list of SVG element components in an object and use the same object to render the SVG element or pass it as prop. So you don't have to pass around the SVG as text/html.
Here is a demonstration: When the user clicks on an SVG element, then selected element will be passed as a prop to the SelectedIcon
component and it will be rendered inside it.
import React, {useState} from "react";
export default ({ text }) => {
const [selectedIcon, setSelectedIcon] = useState(null);
// Store the SVG icons in an object
const icons = {
left: <LeftIcon />,
right: <RightIcon />
}
return (
<>
<h3>SVG Icons</h3>
<div className="flex gap-2 mt-4">
<span className="icon" onClick={() => setSelectedIcon('left')}>
{icons.left}
</span>
<span className="icon" onClick={() => setSelectedIcon('right')}>
{icons.right}
</span>
</div>
<h3>Selected Icon:</h3>
// Here passing the SVG icon component as a prop
<SelectedIcon icon={icons[selectedIcon]} />
</>
)
}
function SelectedIcon({icon}) {
if (! icon) {
return <></>;
}
return (
<span className="icon">
{icon}
</span>
)
}
function LeftIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="20"><path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
)
}
function RightIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="20"><path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>
)
}
Update based on comment:
If you would like to programmatically create the React components from the <svg>
node, then you can use React.createElement. So you can parse svg and dynamically construct the react components for the SVG elements. I believe this is how most svg icon libraries do. Example: FontAwesome