React SVG component as background-image to div
Asked Answered
E

7

38

I am trying to get styled-components to take an SVG that is a react component and set it as a background-image but I get the error:

TypeError: Cannot convert a Symbol value to a string

SVG component code:

import React from "react";

const testSVG = props => {
  return (
    <svg version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 193.3 129.7">
      <g>
        <g>
          <g>
            <path
              fill="#434343"
              class="st0"
              d="M162.4,116c-4.6,7.5-15.6,13.6-24.4,13.6H16c-8.8,0-12.3-6.2-7.7-13.7c0,0,4.6-7.7,11.1-18.4
                c4.4-7.4,8.9-14.7,13.3-22.1c1.1-1.8,6.1-7.7,6.1-10.1c0-7.3-15-24.9-18.5-30.7C13.5,23.6,8.3,14.9,8.3,14.9
                C3.7,7.4,7.2,1.2,16,1.2c0,0,65.9,0,106.8,0c10,0,25.9-4.5,33.5,3.8c8.7,9.3,15.5,25.4,22.5,36.8c2.6,4.2,14.5,18.3,14.5,23.8
                c-0.1,7.6-16,26.1-19.6,32C167.1,108.3,162.4,116,162.4,116z"
            />
          </g>
        </g>
      </g>
    </svg>
  );
};

export default testSVG;

Container component code:

import React, { Component } from "react";
import StepSVG from "../../components/UI/SVGs/Test";

class StepBar extends Component {
  render() {
    const { steps } = this.props;

    return (
      <div>
        {steps
          ? steps.map(step => {
              return (
                <Wrap key={step._id} bg={StepSVG}>
                  <P>
                    {" "}
                    <Link to={"/step/" + step._id}>{step.title} </Link>
                  </P>
                </Wrap>
              );
            })
          : null}
      </div>
    );
  }
}

export default StepBar;

const Wrap = styled.div`
  padding: 30px 30px 0 0;
  display: inline-block;
  background-image: url(${props => props.bg});
`;

I am using create-react-app out of the box.

I also tried without using styled components and used inline styles in place to no success.

Is it possible to use an SVG as a background-image in this context with the SVG being a component?

Emergent answered 9/7, 2018 at 11:27 Comment(0)
R
13

Unfortunately, what you're trying to achieve is not possible. You'll have to keep your SVG as an actual .svg file (and link to that), or do some CSS trickery to place SVG component behind your foreground-component.

In other words:

<!-- step.svg -->
<svg version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 193.3 129.7">
  <!-- copy paste your svg here -->
</svg>

And in your component:

import stepSvgUrl from "../../components/UI/SVGs/step.
// ...
<Wrap key={step._id} bg={stepSvgUrl}>

When you import an SVG file like that, create-react-app applies a Webpack loader that includes the generated URL to the SVG you're importing - which is fine to pass into the background-image css property.

Hope this helps!

Return answered 9/7, 2018 at 11:39 Comment(2)
Thanks. This was my original approach but I am trying to render the colors dynamically. I guess I would just have to create N number of static SVGs for each colour in this approach?Emergent
That's one way to do it, but you can still use your original approach. In that case you'd have to layer the SVG below your content with CSS (the z-index prop will help with that).Return
N
41

For React SVG background images, I find this works best:

// MyComponent.js

import mySvg from './mySvg.svg';

...

function MyComponent() {
  return (
    <div
       className="someClassName"
       style={{ backgroundImage: `url(${mySvg})` }}
    > 

... etc

The path to the SVG file will change when webpack bundles, so by using the template string you can keep things linked up.

In the CSS class you can do whatever styling you like.

Naturopathy answered 4/9, 2019 at 16:6 Comment(2)
the question was about creating an svg as react component, not loading a normal svg from a fileMotheaten
I believe it would be better to clarify that moving the svg into the .svg file would make this a valid solution. Since the svg works only as a background, this is fine.Transparent
R
13

Unfortunately, what you're trying to achieve is not possible. You'll have to keep your SVG as an actual .svg file (and link to that), or do some CSS trickery to place SVG component behind your foreground-component.

In other words:

<!-- step.svg -->
<svg version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 193.3 129.7">
  <!-- copy paste your svg here -->
</svg>

And in your component:

import stepSvgUrl from "../../components/UI/SVGs/step.
// ...
<Wrap key={step._id} bg={stepSvgUrl}>

When you import an SVG file like that, create-react-app applies a Webpack loader that includes the generated URL to the SVG you're importing - which is fine to pass into the background-image css property.

Hope this helps!

Return answered 9/7, 2018 at 11:39 Comment(2)
Thanks. This was my original approach but I am trying to render the colors dynamically. I guess I would just have to create N number of static SVGs for each colour in this approach?Emergent
That's one way to do it, but you can still use your original approach. In that case you'd have to layer the SVG below your content with CSS (the z-index prop will help with that).Return
C
6

I ended up using user11011301's solution in combination with raw-loader to load the SVG content. Not the cleanest solution I'm sure, but I couldn't get it to work with other webpack loaders.

import searchIcon from '../../images/search.svg';

<input style={{backgroundImage: `url(data:image/svg+xml;base64,${btoa(searchIcon)})`}}/>
Charla answered 11/10, 2019 at 9:29 Comment(0)
M
5
url(`data:image/svg+xml;utf8,${svgTxt}`)

;or

url(`data:image/svg+xml;base64,${btoa(svgTxt)}`)

that's it.

Marianamariand answered 4/2, 2019 at 7:49 Comment(0)
I
4

Actually, there is a way to achieve what you want, without the SVG beeing a component:

Here is the updated StepBar component:

import React, { Component } from "react";
import StepSVG from "../../components/UI/SVGs/test.svg";
import encodeSVG from '../../lib/encodeSVG';

class StepBar extends Component {
  render() {
    const { steps } = this.props;

    return (
      <div>
        {steps
          ? steps.map(step => {
              return (
                <Wrap key={step._id} bg={StepSVG}>
                  <P>
                    {" "}
                    <Link to={"/step/" + step._id}>{step.title} </Link>
                  </P>
                </Wrap>
              );
            })
          : null}
      </div>
    );
  }
}

export default StepBar;

const Wrap = styled.div`
  padding: 30px 30px 0 0;
  display: inline-block;
  background-image: ${encodeSVG(bg, '#FF9900')};
`;

Here is encodeSVG lib file:

var symbols = /[\r\n"%#()<>?\[\\\]^`{|}]/g;

function addNameSpace(data) {
  if (data.indexOf('http://www.w3.org/2000/svg') < 0) {
    data = data.replace(/<svg/g, "<svg xmlns='http://www.w3.org/2000/svg'");
  }

  return data;
}

function encodeSVG(data) {
  // Use single quotes instead of double to avoid encoding.
  if (data.indexOf('"') >= 0) {
    data = data.replace(/"/g, "'");
  }

  data = data.replace(/>\s{1,}</g, '><');
  data = data.replace(/\s{2,}/g, ' ');

  return data.replace(symbols, encodeURIComponent);
}

var encode = function(svg, fill) {
  if (fill) {
    svg = svg.replace(/<svg/g, `<svg fill="${fill}"`);
  }
  var namespaced = addNameSpace(svg);
  var dimensionsRemoved = namespaced
    .replace(/height="\w*" /g, '')
    .replace(/width="\w*" /g, '')
    .replace(/height='\w*' /g, '')
    .replace(/width='\w*' /g, '');
  var encoded = encodeSVG(dimensionsRemoved);

  var header = 'data:image/svg+xml,';
  var dataUrl = header + encoded;  

  return `url("${dataUrl}")`;
};

You import the svg, and use a string loader for svgs with your preferred build system (for example WebPack), pass that svg to encodeSVG, et voila! :)

Inocenciainoculable answered 15/11, 2018 at 8:45 Comment(1)
bg variable is not initialized in your example.Pyrite
Q
3

This worked for me:

import * as ReactDOMServer from "react-dom/server";

// svg component
function MySVG({}: Props) {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
      // ...
    </svg>
  );
}

// magic
const svgString = encodeURIComponent(
  ReactDOMServer.renderToStaticMarkup(<MySVG />)
);

// renders
<div style={{ backgroundImage: `url("data:image/svg+xml,${svgString}")` }}>
</div>;

See more: Sandbox demo

Queenstown answered 7/12, 2022 at 3:13 Comment(0)
H
1

You should probably convert it to string (as the error points out). Obviously you cannot use a React component as a background image because the react component is actually a javascript object (JSX converts to JS, then to HTML). So what you can do/try is use, ReactDOMServer (https://reactjs.org/docs/react-dom-server.html).

You could try something with:

ReactDOMServer.renderToString(StepSVG)

or

ReactDOMServer.renderToStaticMarkup(StepSVG)

I haven't tested if this actually works but worth a shot for you I guess :)

Haemato answered 9/7, 2018 at 11:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.