39

I have a dynamically generated SVG string in a React component. I want to embed this as an image in the component. Currently, I'm using something along the lines of:

class SomeComponent extends React.Component {
    render() {
        var image = '<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" width="47.4" height="40.65" viewBox="21 18.5 158 135.5"><path d="M25,50 l150,0 0,100 -150,0 z" stroke-width="4" stroke="black" fill="rgb(128,224,255)" fill-opacity="1" ></path><path d="M25,50 L175,150 M25,150 L175,50" stroke-width="4" stroke="black" fill="black" ></path><g transform="translate(0,0)" stroke-width="4" stroke="black" fill="none" ><circle cx="100" cy="30" r="7.5" fill="black" ></circle><circle cx="70" cy="30" r="7.5" fill="black" ></circle><circle cx="130" cy="30" r="7.5" fill="black" ></circle></g></svg>';
        return (
            <div dangerouslySetInnerHTML={{ __html: image }} />
        )
    }
}

However using a property called dangerouslySetInnerHTML makes me pretty uneasy. Is there a more generally accepted (and safer) way to do this?

James Paterson
  • 2,652
  • 3
  • 27
  • 40
  • 1
    There is a good answer to a similar question here: https://stackoverflow.com/questions/23402542/embedding-svg-into-reactjs – Finbarr O'B Jul 04 '17 at 09:03

7 Answers7

43

Since the SVG is dynamically generated and you can't store it as an asset, as an alternative to dangerouslySetInnerHTML, you could simply set it as a Data URI on the image. So something like...


class SomeComponent extends React.Component {
    render() {
        const image = '<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" width="47.4" height="40.65" viewBox="21 18.5 158 135.5"><path d="M25,50 l150,0 0,100 -150,0 z" stroke-width="4" stroke="black" fill="rgb(128,224,255)" fill-opacity="1" ></path><path d="M25,50 L175,150 M25,150 L175,50" stroke-width="4" stroke="black" fill="black" ></path><g transform="translate(0,0)" stroke-width="4" stroke="black" fill="none" ><circle cx="100" cy="30" r="7.5" fill="black" ></circle><circle cx="70" cy="30" r="7.5" fill="black" ></circle><circle cx="130" cy="30" r="7.5" fill="black" ></circle></g></svg>';
        return (
            <div>
              <img src={`data:image/svg+xml;utf8,${encodeURIComponent(image)}`} />
            </div>
        )
    }
}

See post here: https://css-tricks.com/lodge/svg/09-svg-data-uris/

James P
  • 718
  • 6
  • 14
14

One thing you can do is to convert your svg string to base64 and then use it like this:

const image = '<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" width="47.4" height="40.65" viewBox="21 18.5 158 135.5"><path d="M25,50 l150,0 0,100 -150,0 z" stroke-width="4" stroke="black" fill="rgb(128,224,255)" fill-opacity="1" ></path><path d="M25,50 L175,150 M25,150 L175,50" stroke-width="4" stroke="black" fill="black" ></path><g transform="translate(0,0)" stroke-width="4" stroke="black" fill="none" ><circle cx="100" cy="30" r="7.5" fill="black" ></circle><circle cx="70" cy="30" r="7.5" fill="black" ></circle><circle cx="130" cy="30" r="7.5" fill="black" ></circle></g></svg>';
const buff = new Buffer(image);
const base64data = buff.toString('base64');

return <img src='data:image/svg+xml;base64,${base64data }' alt="" />

if you don't want to use buffer, use this:

const base64data = btoa(unescape(encodeURIComponent(image)));
Soley
  • 1,716
  • 1
  • 19
  • 33
9

Simply use this package: https://github.com/gilbarbara/react-inlinesvg

Example:

import SVG from 'react-inlinesvg';

...    

const mySVG = '<svg xmlns="http://www.w3.org/2000/svg">...</svg>';
return <SVG src={mySVG} />;
Fred Vanelli
  • 662
  • 8
  • 9
5

React ref with innerHTML works quite well and is clean.

var image = '<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" width="47.4" height="40.65" viewBox="21 18.5 158 135.5"><path d="M25,50 l150,0 0,100 -150,0 z" stroke-width="4" stroke="black" fill="rgb(128,224,255)" fill-opacity="1" ></path><path d="M25,50 L175,150 M25,150 L175,50" stroke-width="4" stroke="black" fill="black" ></path><g transform="translate(0,0)" stroke-width="4" stroke="black" fill="none" ><circle cx="100" cy="30" r="7.5" fill="black" ></circle><circle cx="70" cy="30" r="7.5" fill="black" ></circle><circle cx="130" cy="30" r="7.5" fill="black" ></circle></g></svg>';


const useApp = () => {
  const svgWrapperRef = React.useRef();
  React.useEffect(() => {
    svgWrapperRef.current.innerHTML = image;
  }, [])
  return {
    svgWrapperRef
  }
}
const App = () => {
  const {svgWrapperRef} = useApp()
  return (
    <div ref={svgWrapperRef}></div>
  )
}

const root = document.getElementById('root')

ReactDOM.render(<App />, root)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Good Luck...

Aakash
  • 21,375
  • 7
  • 100
  • 81
  • 1
    Despite the attention it received, this is a great solution. No extra library and no unsafe HTML. Worth mentioning, useEffect is required here, otherwise ref.current will be undefined and your app will explode at runtime. – CyberRobot Jan 25 '23 at 10:52
  • @CyberRobot can you please elaborate on the useEffect being required? I'm attempting to follow this solution as I need the SVG to be inline but my app IS in fact exploding at runtime. It seems to happen as soon as the function is called: ``` const createSVG = (svg) => { const svgWrapper = useRef(); useEffect(() => { svgWrapper.current.innerHTML = svg; }, []); return { svgWrapper } }``` – jansyb04 Aug 17 '23 at 20:21
  • In your case, the issue may be with the svg parameter as it is passed down to the function. It is possibly undefined at the time it is assigned to svgWrapper.current.innerHTML. A solution would be to add svg to the useEffect dependency array. Also, add a safeguard for svg inside useEffect so that an undefined value doesn't get assigned svgWrapper.current.innerHTML. – CyberRobot Aug 23 '23 at 13:24
4

In this way, I succeeded.

const svgStr = "<svg></svg>";
const svg = new Blob([svgStr], { type: "image/svg+xml" });
const url = URL.createObjectURL(svg);
<img src={url} />
Jannchie
  • 690
  • 6
  • 18
-1

I know the question is about the string of a svg object, But you can also use the svg object directly.

import React from 'react';

function CustomSvgObject({ }) {
    return <svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" width="47.4" height="40.65" viewBox="21 18.5 158 135.5"><path d="M25,50 l150,0 0,100 -150,0 z" stroke-width="4" stroke="black" fill="rgb(128,224,255)" fill-opacity="1" ></path><path d="M25,50 L175,150 M25,150 L175,50" stroke-width="4" stroke="black" fill="black" ></path><g transform="translate(0,0)" stroke-width="4" stroke="black" fill="none" ><circle cx="100" cy="30" r="7.5" fill="black" ></circle><circle cx="70" cy="30" r="7.5" fill="black" ></circle><circle cx="130" cy="30" r="7.5" fill="black" ></circle></g></svg>
}

You can also dynamically change the svg, like the className property or the color of the svg elements:

import React from 'react';

function CustomSvgObject({ className, color }) {
    return <svg className={className} xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" width="47.4" height="40.65" viewBox="21 18.5 158 135.5"><path d="M25,50 l150,0 0,100 -150,0 z" stroke-width="4" stroke={color} fill="rgb(128,224,255)" fill-opacity="1" ></path><path d="M25,50 L175,150 M25,150 L175,50" stroke-width="4" stroke={color} fill={color} ></path><g transform="translate(0,0)" stroke-width="4" stroke={color} fill="none" ><circle cx="100" cy="30" r="7.5" fill={color} ></circle><circle cx="70" cy="30" r="7.5" fill={color} ></circle><circle cx="130" cy="30" r="7.5" fill={color} ></circle></g></svg>
}
Mohsenasm
  • 2,916
  • 1
  • 18
  • 22
-4

I would store the svg image in a separate folder(assets), and import the image into the react component

Something like this:

SomeComponent.js:

import { SampleImage } from '../assets/SomeFile';

class SomeComponent extends React.Component {
    render() {

        return (
            <div>
              <img src={SampleImage} />
            <div/>
        )
    }
}

SomeFile.svg:

<?xmlns="http://www.w3.org/2000/svg" version="1.2"encoding="UTF-8"?>

 <svg baseProfile="tiny" width="47.4" height="40.65" viewBox="21 18.5 158 135.5"><path d="M25,50 l150,0 0,100 -150,0 z" stroke-width="4" stroke="black" fill="rgb(128,224,255)" fill-opacity="1" ></path><path d="M25,50 L175,150 M25,150 L175,50" stroke-width="4" stroke="black" fill="black" ></path><g transform="translate(0,0)" stroke-width="4" stroke="black" fill="none" ><circle cx="100" cy="30" r="7.5" fill="black" ></circle><circle cx="70" cy="30" r="7.5" fill="black" ></circle><circle cx="130" cy="30" r="7.5" fill="black" >
    </circle></g>
 </svg>
eagercoder
  • 526
  • 1
  • 10
  • 23
  • 2
    This is the normal way to work with svgs. I have a string and want to generate an element out of it. the strings come from server – Soley Nov 22 '19 at 09:19
  • Is it possible to set style on this dynamicaly render svg? – TuMama Jan 12 '23 at 10:22