Create-react-app: Consider importing SVGs as React components

Created on 12 Jan 2017  ·  78Comments  ·  Source: facebook/create-react-app

My own path of using SVGs with webpack has settled me comfortably with loading .svg files as react components via https://github.com/jhamlet/svg-react-loader (svg-react-loader@next as of time of writing). This allows you to treat an SVG just like any other React component, and renders out inline SVGs. For example:

const MySvg = require('./mySvg.svg')

const MyApp = () =>
  <div>
      // some components!
     <MySvg /> // could also pass fill='#ccc' or style={styleObj}
  </div>

// renders
/*
<div>
  <svg ...> ...etc </svg>
</div>
*/

Inline SVGs offer the most amount of flexibility in terms of styling, animation, and manipulating props. SVGs in the browser are now more supported than ever. They are quickly replacing icon fonts as the preferred way to do icons in your app.

I'd enjoy hearing the community's thoughts on this.

proposal

Most helpful comment

This video convinced me that the way we should be using SVG is inline. This table, showing the features and benefits of implementing SVG using the three available techniques, cinched it for me:

screen shot 2017-01-13 at 10 45 27 am

An SVG is different from all other assets (image, sound, video, etc) in that isn't a binary blob, and so can be more easily manipulated in our app. An SVG has more in common with a stateless react component than a JPEG.

You can still do this:

import Logo from './logo.svg';
...
<Logo className='App-logo' ariaLabel='logo' />
...

Then again, I suppose I haven't thought through the scenario of using an SVG asset in CSS…
😬

All 78 comments

So, for example, on the starting page, instead of

import logo from './logo.svg';
...
<img src={logo} className="App-logo" alt="logo" />
...

it would be

import Logo from './logo.svg';
...
<Logo />
...

Isn't that a little opinionated?

This video convinced me that the way we should be using SVG is inline. This table, showing the features and benefits of implementing SVG using the three available techniques, cinched it for me:

screen shot 2017-01-13 at 10 45 27 am

An SVG is different from all other assets (image, sound, video, etc) in that isn't a binary blob, and so can be more easily manipulated in our app. An SVG has more in common with a stateless react component than a JPEG.

You can still do this:

import Logo from './logo.svg';
...
<Logo className='App-logo' ariaLabel='logo' />
...

Then again, I suppose I haven't thought through the scenario of using an SVG asset in CSS…
😬

I've ran into a similar problem/issue, I'd like to be able to reference SVGs and then adjust their fill colour based on some API call response. You can't do this with an external reference to the SVG, you'd need to inline them. Is there a way right now to basically get the SVG text and even just dump it in?

as a workaround you can do something like:

import React from 'react';
export default () =>
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
    <path d="M2.001 9.352c0 1.873.849 2.943 1.683 3.943.031"/>
  </svg>

then you can take in props and use those to adjust the fill

@kauffecup I'm new to ES6, React and this library so apologies if silly questions... Are you saying make a file called something.js that is essentially a wrapper around an SVG? How would you call this in another file.

Also, thank you.

@kauffecup I tried your method of inlining an SVG into a component, but am seeing a webpack hot dev client error that it cannot build the module...

screen shot 2017-01-28 at 7 04 38 pm

Any ideas what might be causing this?

Update for anybody else seeing this: the issue was resolved when I removed namespaces from the svg, since jsx is not xml, they cause errors.

@kauffecup that's adding extra js cruft to what should be a standalone svg resource. Not ideal.

@edhenderson I think using svg-react-loader that I mention in the OP will get you what you want. It's working perfectly for me, of course you have to eject from create-react-app first.

I appreciate the suggestion but I know some people are already using SVGs as sprites so I wouldn't like to break them. Especially given that it is easy to declare SVG inside a React component anyway.

We have some options:

1

Assume we have components/Pin.svg it can be transformed (by webpack, in memory) to:

export default "components/Pin.hash.svg"

export function Pin(props) {
  return (
    <svg ...>...</svg>
  )
}

This way it will backward compatible with current CRA convention about SVGs.

Advantage of using SVGs directly (instead of copy-pasting it to React component) is that you can edit it in visual editor, like Inkscape.

2

Create CLI tool which will generate (and save to the disk) React Components from SVG files. It additionally can use svgmin. Developer can edit generated files afterwords or regenerate components whenever SVG changes.

PS Github uses inline SVGs for icons

I feel like this issue has been closed prematurely. The main motivation seems to be BC (which is understandable) but the following statement is not necessarily true:

Especially given that it is easy to declare SVG inside a React component anyway.

While it is easy to declare SVG in React code, that means it's all the more complicated to work with actual SVG files if you want them inline (as is the current recommendation).

You basically need to manually create one component per icon and paste the SVG contents in them (and god knows some app can have a lot of icons), accounting for attributes changes (class => className) and such.
That or run some kind of third party script to do the conversion for you; in both cases you run the risk of your React SVG components getting out of sync with the actual SVG files, which is what your designer is gonna work with and give to you.

My point is, considering the current recommendation seems to be to use inline SVGs it seems counterproductive for the default behavior of CRA to be using <img> tags – all the more since CRA is all about convention over configuration and sane defaults. In that spirit I think it'd better long term to not prioritize BC over good defaults.

Agree with @Anahkiasen , I found this looking for a solution to using inline SVGs without ejecting and modifying the Webpack loader, and was disappointed to find that loading them as images was the expected behavior. I understand the reasoning, but the future-proof path and what I've done on every other project is Graphics Program => SVGO => Webpack loader => inline in document.

Thanks for chiming in and keeping this discussion going. The fact that new people keep finding their way to this thread means more and more people expect CRA to allow them to:

  1. Keep their SVGs as individual SVG files, compatible with any SVG editor
  2. Use those SVGs as React components for the sake of passing props
  3. Avoid sending unnecessary icons/vectors to the client as a result of using an unprocessed sprite sheet
  4. Having to manually maintain a sprite sheet in addition to a React dev environment like CRA

Handling svgs as their own media type with their own loader allows for all of this. I hope we can re-open this issue one day.

I realized that my first comment is not clear on what I'm proposing. My solution (1) is backward compatible with existing convention.

import Pin from 'Pin.svg'; // you get svg url or base64 encoded data url
// which is the same as
import { default as Pin } from 'Pin.svg'; 

// but if you do this

import { Pin } from 'Pin.svg'; // you get React component with inline svg

I achieved this using https://github.com/jhamlet/svg-react-loader explicitly on the one SVG I wanted to inline.

import Logo from '-!svg-react-loader!./logo.svg';

The -! allows it to bypass the existing loaders so I can get away without ejecting.

@gwagener I'm getting module resolve issues when using -!svg-react-loader!../path/to/file.svg;
Are there any additional steps required?

@kaumac Hm, I'm actually using create-react-app-ts so I had to add a custom.d.ts with

declare module '*.svg' {
  const content: string;
  export default content;
}

For the type definitions of SVG. Without that I also got module resolve issues. Also, I'm still getting module resolve issues testing with Jest.

Hm, have tested in vanilla CRA instead of TypeScript and see that my suggestion won't work because Webpack loader syntax isn't allowed. ⏏

This worked for me after installing raw-loader.

// Icon.js
const Icon = ({ icon,  ...props }) => {
  const svg = require(`!raw-loader!./icons/${icon}.svg`);
  return <span {...props} dangerouslySetInnerHTML={{__html: svg}}/>;
};

Then I just use something like <Icon icon="checkmark" className="icon"/>. I originally saw this pattern in a boilerplate but I can't remember which one...

@kme211

Please note that using Webpack loaders like this in a Create React App project is not supported and can break in any patch release. If you want to use Webpack loaders directly please eject (unless you’re okay with potential breakage in the future).

Regarding proposal in https://github.com/facebookincubator/create-react-app/issues/1388#issuecomment-308054716, it’s a bit counter intuitive to me. People are already confused by naming vs default exports, and I think repurposing it for an escape hatch doesn’t help.

Let’s reopen as this seems popular. It’s not clear to me yet how to approach it in a way that wouldn’t break existing use cases.

@gaearon does not break existing case

import Pin from 'Pin.svg'; // you get svg url or base64 encoded data url
// which is the same as
import { default as Pin } from 'Pin.svg'; 

// but if you do this

import { Pin } from 'Pin.svg'; // you get React component with inline svg

Yes, but as I mentioned in https://github.com/facebookincubator/create-react-app/issues/1388#issuecomment-313976907, it’s pretty confusing. Maybe that’s okay for an escape hatch. If you send a PR for this, I can take a good look and make a more informed decision.

I should point out that any of the above proposed workarounds that use custom loader syntax no longer work due to #803.

@gaearon It's not a scape hatch, it's a discriminator on when to use the svg React component or when not to. I can't see a more elegant way for it. True that can be misleading, but documentation could minimize it.

As an alternative to the default vs named import, how about updating the CRA webpack config to exclude a given extension (__.inline.svg__?) from url-loader/file-loader; and handling that extension with svg-react-loader.

eg.

// webpack.config
loaders: [
    {
         test: /\.(svg|png|jpg|etc)$/,
         exclude: [/\.inline\.svg$/],
         use: ['url-loader']
    },
    {
         test: /\.inline\.svg$/,
         use: ['svg-react-loader']
    }
]

I like this. How about .component.svg? Could also do .component.html while we're at it since SVG is only a subset of valid things you can put in innerHTML. Would you like to raise a new issue with more detailed proposal?

Or maybe .react.svg and .react.html.

I filed https://github.com/facebookincubator/create-react-app/issues/2961.

If we implement this, it's the most likely way forward, so I'll close this issue.

+1 for .component.(svg|html). .react.svg implies you'll be using the asset in a react app forever, but there are plenty of other frameworks where the same svg could be used as a 'component'.

really glad I posted this issue!

I have a question for you fine people, including @gaearon. I've been able to render some <svg>s from React:

https://github.com/mocon/svg-constellations-react

However, I would like to animate some of the attributes within the SVGs, such as the radius of the circles. I tried to do so using the animated library (see the example component below), but React gets mad and doesn't allow the returning of non-SVG elements (in my case, an <Animated.svg />).

Does anyone have any tips on how to animate an internal SVG attribute? Many thanks in advance!

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Animated from 'animated';

class Star extends Component {
    constructor() {
        super();
        this.state = {
            coronaWidth: new Animated.Value(4.8)
        };
    }

    componentDidMount() {
        Animated.spring(this.state.coronaWidth, { toValue: 8 }).start();
    }

    render() {
        const { width, height, xPos = 0, yPos = 0 } = this.props;

        return (
            <Animated.svg
                width={ `${width}px` }
                height={ `${height}px` }
                x={ `${xPos - (width / 2)}px` }
                y={ `${yPos - (height / 2)}px` }
                version="1.1"
                viewBox="0 0 43.9 43.9"
                style={ { 'enableBackground': 'new 0 0 43.9 43.9' } }
                xmlSpace="preserve"
            >
                <filter id="blurInner" x="-100%" y="-100%" width="200%" height="200%">
                    <feGaussianBlur in="SourceGraphic" stdDeviation="0.3" />
                </filter>
                <filter id="blurOuter" x="-100%" y="-100%" width="400%" height="400%">
                    <feGaussianBlur in="SourceGraphic" stdDeviation="3.5" />
                </filter>
                <filter id="blurTail">
                    <feGaussianBlur in="SourceGraphic" stdDeviation="0.5" />
                </filter>
                <circle className="star__outer-glow" filter="url(#blurOuter)" cx="22" cy="22" r={ this.state.coronaWidth } />
                <path className="star__tail" filter="url(#blurTail)" d="M22,0c0,0-0.5,20.7-1.2,22H22h1.2C22.4,20.7,22,0,22,0z"/>
                <path className="star__tail" filter="url(#blurTail)" d="M22,43.9c0,0,0.5-20.7,1.2-22H22h-1.2C21.5,23.2,22,43.9,22,43.9z"/>
                <path className="star__tail" filter="url(#blurTail)" d="M43.9,22c0,0-20.7-0.5-22-1.2V22v1.2C23.2,22.4,43.9,22,43.9,22z"/>
                <path className="star__tail" filter="url(#blurTail)" d="M0,22c0,0,20.7,0.5,22,1.2V22v-1.2C20.7,21.5,0,22,0,22z"/>
                <circle className="star__inner-glow" filter="url(#blurInner)" cx="22" cy="22" r="3"/>
            </Animated.svg>
        );
    }
}

Star.propTypes = {
    width: PropTypes.number,
    height: PropTypes.number,
    xPos: PropTypes.number,
    yPos: PropTypes.number
};

export default Star;

I do not know if this issue is still under consideration. I had the same problem and I made svgr. If you are OK I could submit a PR.

I am using this popular library to load SVGs: https://www.npmjs.com/package/react-svg-loader

import IconEnvelope from '-!react-svg-loader!./envelope.svg';

Thanks for the tricky -! with react-svg-loader, helps a ton.

This is how I did it, it's also required to disable the eslint for that particular line, or an error will be thrown at compilation. ("react-scripts": "1.0.14")

import IconLogo from "-!react-svg-loader!../assets/logo.svg"; // eslint-disable-line import/no-webpack-loader-syntax

FYI, my use case was to load the company logo, an SVG file and display it, that's it. I wish it had been easier to do, rather than installing a 3rd party software and figure out how to do the setup without ejecting. ;)

@Vadorequest unfortunately it breaks production build. Please look my question and my answer on StackOverflow :D

I haven't tested it in production yet...

But I just found an online app to convert non-react SVG to react valid SVG, very useful:
https://github.com/balajmarius/svg-to-jsx-gui

I appreciate the suggestion but I know some people are already using SVGs as sprites so I wouldn't like to break them. Especially given that it is easy to declare SVG inside a React component anyway.

Backwards compatibility should never be the reason to prevent features from being deployed. Apps don't auto-update to newer react versions, which allows developers for fixes if they were using it differently.

Not implying that it's valid in this case, SVG is XML namespace. It should work the same way as import what from './something.html' so...

I haven't tested it in production yet..

Just run yarn build. Very interesting - is it will work for you or not.

@inferusvv Indeed...

Creating an optimized production build...
Failed to compile.

Failed to minify the code from this file:

        ./node_modules/react-svg-loader/lib/loader.js!./src/assets/logo.svg:6

Read more here: http://bit.ly/2tRViJ9


npm ERR! Linux 4.4.0-81-generic
npm ERR! argv "/root/.nvm/versions/node/v6.12.0/bin/node" "/root/.nvm/versions/node/v6.12.0/bin/npm" "run" "build"
npm ERR! node v6.12.0
npm ERR! npm  v3.10.10
npm ERR! code ELIFECYCLE
npm ERR! [email protected] build: `npm run build-css && react-scripts build`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] build script 'npm run build-css && react-scripts build'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the student-loan-simulator package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     npm run build-css && react-scripts build
npm ERR! You can get information on how to open an issue for this project with:
npm ERR!     npm bugs student-loan-simulator
npm ERR! Or if that isn't available, you can get their info via:
npm ERR!     npm owner ls student-loan-simulator
npm ERR! There is likely additional logging output above.

Gotta find a new way to use SVG, such a PAIN.

import IconEnvelope from '-!react-svg-loader!./envelope.svg';

Answers with tricks like !- are likely to randomly break in future versions. Really, if you're trying to work around the tool so hard, you should just eject and customize the tool as you like it.

Backwards compatibility should never be the reason to prevent features from being deployed.

Auto updating is not my concern. It's that it's bad to make an existing use case impossible.


I'm reopening because I think I'm happy with using an explicit names export for this.

Something like

import { ReactComponent as MyButton } from './Button.svg';

@gaearon you mean:

import { ReactComponent as MyButton } from './Button.svg' // get React Component
import Button from './Button.svg' // get something else?

I didn't catch what is the benefit of a named export instead of using the default one?

I am interested because I took the approach of default export in SVGR and I do not have any issue about it.

Yes.

I didn't catch what is the benefit of a named export instead of using the default one?

Because the default one gives you the URL, just like with any other kind of image. If we used default export for this, we'd make existing use cases broken (people already depend on current behavior).

A named export is both explicit (so you can search for what it means), and is backwards compatible.

@gaearon I've been watching this issue for a while and am interested in doing this. I'm not entirely sure how the named export would be implemented though. If you can point me in the right direction I can start working on it.

I'm not really sure. Maybe @neoziro has ideas? Ideally I'd like this to be an option for svgr webpack loader.

So in your example Button.svg is just a plain SVG file and importing it with a named import causes webpack to use the svgr loader instead of the regular SVG loader?

Yeah. Or, maybe we always use svgr loader but it has a special mode in which it re-exports the result of a previous loader through default export.

I was thinking something along the same lines: have svgr export both the raw SVG as the default export and the component as a named export. However, as you point out, the default isn't just the raw SVG it's actually the URL of the file as generated by another loader.

It looks like the svgr template option might be useful here: https://github.com/smooth-code/svgr#template

@neoziro any thoughts on this?

@gaearon I understand what do you want to achieve.

@iansu yes template could help, we could inspire from lqip-loader (https://github.com/zouhir/lqip-loader/blob/master/index.js), it supports url-loader and file-loader out of the box.

That's helpful. So the URL from file-loader would get passed as the input to the svgr loader and we could access the original SVG file using this.resourcePath inside the loader. The next step would be passing both of those into the template function.

@neoziro that seems like kind of a big change to svgr. Is that something you want? Alternatively we could create a new webpack loader that worked this way and used svgr internally.

@iansu yes I think it is a good option to be able to use it with file-loader or url-loader. It will not be a breaking change and it will be a great addition!

Do you want to work on it?

@neoziro Sure, I can work on it. I think with these changes to svgr the changes to CRA's webpack config would be pretty straightforward. Just add a loader entry for .svg and use file-loader then svgr. The only issue I see is that this increases the size of SVG imports. @gaearon what do you think about this approach?

My thinking was that if we use ES6 exports then tree shaking should just ignore unused exports (either component, file URL, or both).

I did it https://github.com/smooth-code/svgr/pull/32:

When you use url-loader + svgr/webpack or file-loader + svgr/webpack:

import { ReactComponent }, url from './file.svg'

When you use only svgr/webpack:

import ReactComponent from './file.svg'

Does it fit your needs? @gaearon @iansu

@neoziro I was just about to send you a PR. Your change looks very similar to mine so I think it should work. I can test it with CRA and let you know for sure.

@neoziro Looks like it does work. 👍

If you can publish a new version of svgr I can commit the necessary create-react-app changes.

@iansu thanks, I will do it.

Please participate in a wider discussion about this approach: https://github.com/facebookincubator/create-react-app/issues/3722.

use:

xlink:href => xlinkHref
working...

problem is == > Syntax error: Namespace tags are not supported. ReactJSX is not XML.

Support for this has been released in the first v2 alpha version. See https://github.com/facebookincubator/create-react-app/issues/3815 for more details!

I'm sorry to add noise, but what is considered the current stable best practice for inline SVGs? I got my SVGs from a designer (over 100 of them) and would not like to convert them to JSX. My react-scripts is at version 1.0.17 and I have no problem updating to a more recent stable version if it will make the difference.

@rivertam you can convert them statically using svgr, I think this is the simplest thing to do. This is what I do in all of my projects.

svgr -d src/components --icon src/svgs

@neoziro I don't love it, but it'll work for me. Thanks!

as a workaround you can do something like:

import React from 'react';
export default () =>
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
    <path d="M2.001 9.352c0 1.873.849 2.943 1.683 3.943.031"/>
  </svg>

then you can take in props and use those to adjust the fill

I've gone with this approach, keeping my svgs in one file and importing them where needed, for example:

import React from 'react';

const Logo = () => (
  <svg viewBox="0 0 636 76">
    <g
      id="Page-1"
      stroke="none"
      stroke-width="1"
      fill="none"
      fill-rule="evenodd"
      font-size="111"
      font-family="Limelight"
      letter-spacing="3.80571437"
      font-weight="normal"
    >
      <text id="logo" fill="#313131">
        <tspan x="-1.59871466" y="76">
          LOGO
        </tspan>
      </text>
    </g>
  </svg>
);

export { Logo };

It's then imported as a complete standalone svg with full control.

This was released in 2.0 stable. Workarounds are no longer needed.

There is some bug or limitation you have to keep in mind when you have multiple svg's on one page. You need to check for unique id's in svg file path's and mask's as second, third svg will grab first match id from page. So you can find all svg's to be the same as first in the document.

Probably my answer will be helpful anybody

Add .svg extension to extenstions list for url-loader

          {
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/],
            loader: require.resolve("url-loader"),
            options: {
              limit: 10000,
              name: "static/media/[name].[hash:8].[ext]"
            }
          }

Import .svg like that

import * as React from "react";
import { Icon } from "components";
import * as downArrow from "common/assets/down-arrow.svg";

const CollapseIcon = ({ condition }) => (
  <Icon asset={downArrow}  />
);

export default CollapseIcon;

Current props can use in src attribute, example, for my code

const Icon = styled.img.attrs<IconProps>({
  src: ({ asset }: IconProps) =>  asset,
  alt: ({ alt }: IconProps) => alt
})`
  vertical-align: middle;
`

@ravecat you can just use:

import { ReactComponent as WhateverNameYouWant } from 'common/assets/down-arrow.svg';

and you get a nice inlined SVG.

How about having svg components pure/memoized by default?

At the moment to achieve the same I would have to do something like this

import React, { PureComponent, memo } from 'react'
import { ReactComponent as _GraphIcon } from '../../assets/icons/icon-graph.svg'
const GraphIcon = memo(_GraphIcon)

wouldn't it be better if this was done by default?

@khaledosman Please create a new issue.

It seems that

export { ReactComponent as MyIcon } from './icon.svg'

produces undefined when exported as

import { MyIcon } from 'icons/index.js'

@AlpacaGoesCrazy don't think you can do that, it's not a real import. It's using a Webpack plugin to transform the statement. You'll have to import { ReactComponent as BlahBlah } from './blah.blah' and then export the result.

@philraj thank you, just did that and it works

import { ReactComponent as MyIcon } from './icon.svg'
export {
    MyIcon
}

however it would be nice to use direct exports for svg

This has been fixed but hasn't been released yet: https://github.com/facebook/create-react-app/pull/5573

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Evan-GK picture Evan-GK  ·  3Comments

Aranir picture Aranir  ·  3Comments

rdamian3 picture rdamian3  ·  3Comments

xgqfrms-GitHub picture xgqfrms-GitHub  ·  3Comments

stopachka picture stopachka  ·  3Comments