Render SVGSVGElement in React JS without dangerouslySetInnerHtml
Asked Answered
P

2

13

Question: Can I render an SVGSVGElement in React without using dangerouslySetInnerHtml?

Context:

I am using the vis.js graph library, the getLegend method returns an SVGSVGElement object i.e. const icon = chart.getLegend(args); In the console I can see this:

in: icon instanceof SVGSVGElement
out: true
in: icon
out: <svg><rect x="0" y="0" width="30" height="30" class="vis-outline"></rect><path class="vis-graph-group0" d="M0,15 L30,15"></path></svg>

Problem:

When I try to render this in react using:

render (
<div> { icon } </div>
)

I get the following error:

Error: Objects are not valid as a React child (found: [object SVGSVGElement]). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `LegendElement`

Workaround:

For now I am using: <svg dangerouslySetInnerHTML={{__html: icon.innerHTML}} />

But I was hoping for a straightforward solution that doesn't use a method with the word dangerous in the name.

Research:

I read this similar question but I don't think it helps for SVG generated at run time: How do I use an SVG in React without using dangerouslySetInnerHTML?

Perforation answered 25/8, 2017 at 8:13 Comment(0)
G
13

You can simple append the SVGSVGElement using a useRef like this. This example is for a functional component with hooks but can be adapted for class components.

const svg = useRef(null);
useEffect(()=>{
    if(svg.current){
        svg.current.appendChild(icon)
    } 
}, []);

return (
    <div ref={svg}/>
);
Goltz answered 20/4, 2020 at 1:42 Comment(6)
Thanks Ihsan,If anyone can verify this and let me know to mark the answer. I no longer have access to the project I needed this for and don't have time to set up a PoC :DPerforation
This sort of works, but the SVG is like squished until I click on it. But that is probably due to something else (I'm trying to get a directed force graph from D3 to render in there)Adon
I finally got the chance to validate this answer and it works perfectly at least in my case. Thanks Ishan!Perforation
@Asu; did you ever get this to work for your force graph? I have the same problem.Mercantilism
@Mercantilism Yes, I had to add this just before render's return statement: if (d3Chart.simulation && d3Chart.simulation.restart) {d3Chart.simulation.restart();}Adon
It rendered two svg icons. So, I changed the if condition like this. if (svg.current && (svg.current as any).innerHTML === "")Ceroplastics
D
1

During development, it is very common to re-render the svg, so I had to do this:

  const icon = (...); // SVGSVGElement
  const svg = useRef(null);

  useEffect(() => {
    if (svg.current) {
      if (svg.current.firstChild) {
        svg.current.replaceChild(icon, svg.current.firstChild)
      } else {
        svg.current.appendChild(icon)
      }
    }
  }, []);

  return <div ref={svg} />

This code prevents from rendering 2+ times.

Durden answered 23/12, 2023 at 14:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.