18

I have some SVGs that have a defs attribute with a style tags inside. Like this:

<svg ...>
  <defs>
    <style>.cls-1,.cls-7{fill:#b2488d;}.cls-1,.cls-2,.cls-3,.cls-4,.cls-5,.cls-6{stroke:#671f4d;}</style>
  </defs>
  ...
</svg>

I want to use these SVGs in React, so I want to convert them to valid JSX. I already used tools like svg2jsx, but they strip the defs tag away so none of the style attributes are present anymore. Is there a way to preserve the defs with the style tag inside by converting the SVG in JSX? Or is it not possible to use css classes in this case?

feychu
  • 1,284
  • 1
  • 14
  • 33

2 Answers2

20

You can preserve the styles without any conversion. For that wrap all the CSS classes inside the style tag with {` and `}. Now your SVG becomes like this

<svg ...>
  <defs>
    <style>{`.cls-1,.cls-7{fill:#b2488d;}.cls-1,.cls-2,.cls-3,.cls-4,.cls-5,.cls-6{stroke:#671f4d;}`}</style>
  </defs>
  ...
</svg>

This will render without any problem.

Nipun
  • 990
  • 1
  • 16
  • 25
10

If you created your SVG in Illustrater, save it with CSS Properties set to Presentation Attributes. This way, you won't end up with a CSS classes and you are able to directly alter all attributes.

I exported an SVG that looks something like this:

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<g>
    <rect x="15.5" y="15.5" fill="#FFFFFF" width="44" height="44"/>
    <path d="M59,16v43H16V16H59 M60,15H15v45h45V15L60,15z"/>
</g>
<g>
    <path fill="#FFFFFF" d="M60.5,81.5c-12.1,0-22-9.9-22-22s9.9-22,22-22s22,9.9,22,22S72.6,81.5,60.5,81.5z"/>
    <path d="M60.5,38C72.4,38,82,47.6,82,59.5S72.4,81,60.5,81S39,71.4,39,59.5S48.6,38,60.5,38 M60.5,37C48.1,37,38,47.1,38,59.5
        S48.1,82,60.5,82S83,71.9,83,59.5S72.9,37,60.5,37L60.5,37z"/>
</g>
</svg>

I then got rid of all unneeded markup and just used it inside my component:

const Image = ( props ) => {
  const {
    hideSquare,
    hideCircle,
  } = props;
  
  const colorSquare = props.colorSquare || '#fff';
  const colorCircle = props.colorCircle || '#fff';
  
  return (
    <svg x="0px" y="0px" viewBox="0 0 100 100">
      { hideSquare ? null : (
        <g>
          <rect x="15.5" y="15.5" fill={ colorSquare } width="44" height="44"/>
          <path d="M59,16v43H16V16H59 M60,15H15v45h45V15L60,15z"/>
        </g>
      ) }
      { hideCircle ? null : (
        <g>
          <path fill={ colorCircle } d="M60.5,81.5c-12.1,0-22-9.9-22-22s9.9-22,22-22s22,9.9,22,22S72.6,81.5,60.5,81.5z"/>
          <path d="M60.5,38C72.4,38,82,47.6,82,59.5S72.4,81,60.5,81S39,71.4,39,59.5S48.6,38,60.5,38 M60.5,37C48.1,37,38,47.1,38,59.5
            S48.1,82,60.5,82S83,71.9,83,59.5S72.9,37,60.5,37L60.5,37z"/>
        </g>
      ) }
    </svg>
  );
};

class Wrapper extends React.Component {
  constructor( props ) {
    super( props );
    
    // Set default state
    this.state = {
      selectedColor: 'lightgreen',
      hideSquare: false,
      hideCircle: false,
    };
  }
  
  // onInput callback
  changeColor = ( e ) => {
    this.setState( { selectedColor: e.target.value } );
  }
  
  changeVisibility = ( e ) => {
    const { name, checked } = e.target;
    this.setState( { [ name ]: checked } );
  }
  
  render() {
    return (
      <div>
        <select onInput={ this.changeColor }>
          <option>lightgreen</option>
          <option>pink</option>
          <option>red</option>
        </select><br />
        <label><input type="checkbox" name="hideSquare" onChange={ this.changeVisibility } /> hideSquare</label>
        <label><input type="checkbox" name="hideCircle" onChange={ this.changeVisibility } /> hideCircle</label><br />
        <Image
          hideSquare={ this.state.hideSquare }
          hideCircle={ this.state.hideCircle }
          colorSquare={ this.state.selectedColor }
          colorCircle={ this.state.selectedColor }
        />
      </div>
    );
  }
}

ReactDOM.render( <Wrapper />, document.getElementById( 'app' ) );
svg {
  width: 200px;
  height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
lumio
  • 7,428
  • 4
  • 40
  • 56
  • Would the latter allow me to change attribute values with React state values? As in, would string interpolation work? – feychu Oct 10 '17 at 15:18
  • I updated my answer. tl;dr: You need to export your SVG without generating CSS styles. :) – lumio Oct 10 '17 at 15:47
  • This does not exactly answer my question, however I found it to be a good workaround for my issue. – feychu Oct 17 '17 at 15:44