Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider importing SVGs as React components #1388

Closed
ericdfields opened this issue Jan 12, 2017 · 78 comments
Closed

Consider importing SVGs as React components #1388

ericdfields opened this issue Jan 12, 2017 · 78 comments
Milestone

Comments

@ericdfields
Copy link

ericdfields commented Jan 12, 2017

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.

@wtgtybhertgeghgtwtg
Copy link
Contributor

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?

@ericdfields
Copy link
Author

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…
😬

@edhenderson
Copy link

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?

@kauffecup
Copy link

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

@edhenderson
Copy link

@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.

@kristojorg
Copy link

@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?

@kristojorg
Copy link

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.

@ericdfields
Copy link
Author

@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.

@gaearon
Copy link
Contributor

gaearon commented Feb 11, 2017

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.

@gaearon gaearon closed this as completed Feb 11, 2017
@stereobooster
Copy link
Contributor

stereobooster commented May 6, 2017

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

@Anahkiasen
Copy link

Anahkiasen commented May 30, 2017

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.

@jjmartucci
Copy link

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.

@ericdfields
Copy link
Author

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.

@stereobooster
Copy link
Contributor

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

@gwagener
Copy link

gwagener commented Jun 19, 2017

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.

@kaumac
Copy link

kaumac commented Jun 20, 2017

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

@gwagener
Copy link

gwagener commented Jun 20, 2017

@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.

@gwagener
Copy link

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

@kme211
Copy link

kme211 commented Jul 9, 2017

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...

@gaearon
Copy link
Contributor

gaearon commented Jul 10, 2017

@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).

@gaearon
Copy link
Contributor

gaearon commented Jul 10, 2017

Regarding proposal in #1388 (comment), 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.

@gaearon
Copy link
Contributor

gaearon commented Jul 10, 2017

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.

@stereobooster
Copy link
Contributor

stereobooster commented Jul 10, 2017

@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

@gaearon
Copy link
Contributor

gaearon commented Jul 10, 2017

Yes, but as I mentioned in #1388 (comment), 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.

@brianchirls
Copy link

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

@sneridagh
Copy link

@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.

@at0g
Copy link

at0g commented Aug 16, 2017

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']
    }
]

@gaearon
Copy link
Contributor

gaearon commented Jan 9, 2018

Please participate in a wider discussion about this approach: #3722.

@onopko81
Copy link

use:

xlink:href => xlinkHref
working...

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

gaearon pushed a commit that referenced this issue Jan 17, 2018
* Import SVGs as React components (#1388)

* Updated webpack production config and fixed tests

* Improved Jest SVG file transform

* Improved SVG tests

* Add a comment

* Update webpack.config.prod.js
@gaearon
Copy link
Contributor

gaearon commented Jan 17, 2018

This is now implemented in #3718.

@Timer
Copy link
Contributor

Timer commented Jan 18, 2018

Support for this has been released in the first v2 alpha version. See #3815 for more details!

@gaearon
Copy link
Contributor

gaearon commented Jan 18, 2018

This might not make it after all 😞 #3856

akstuhl pushed a commit to akstuhl/create-react-app that referenced this issue Mar 15, 2018
* Import SVGs as React components (facebook#1388)

* Updated webpack production config and fixed tests

* Improved Jest SVG file transform

* Improved SVG tests

* Add a comment

* Update webpack.config.prod.js
yoiang referenced this issue in Adorkable-forkable/create-react-app-typescript May 2, 2018
* Import SVGs as React components (#1388)

* Updated webpack production config and fixed tests

* Improved Jest SVG file transform

* Improved SVG tests

* Add a comment

* Update webpack.config.prod.js

# Conflicts:
#	packages/react-scripts/package.json
@rivertam
Copy link

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.

@gregberge
Copy link

@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

@rivertam
Copy link

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

zmitry pushed a commit to zmitry/create-react-app that referenced this issue Sep 30, 2018
* Import SVGs as React components (facebook#1388)

* Updated webpack production config and fixed tests

* Improved Jest SVG file transform

* Improved SVG tests

* Add a comment

* Update webpack.config.prod.js
@and-why
Copy link

and-why commented Oct 6, 2018

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.

@Timer
Copy link
Contributor

Timer commented Oct 6, 2018

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

@ingelaro
Copy link

ingelaro commented Oct 10, 2018

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.

@ravecat
Copy link

ravecat commented Oct 17, 2018

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;
`

@philraj
Copy link

philraj commented Nov 20, 2018

@ravecat you can just use:

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

and you get a nice inlined SVG.

@khaledosman
Copy link

khaledosman commented Dec 3, 2018

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?

@iansu
Copy link
Contributor

iansu commented Dec 3, 2018

@khaledosman Please create a new issue.

@AlpacaGoesCrazy
Copy link

It seems that

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

produces undefined when exported as

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

@philraj
Copy link

philraj commented Dec 11, 2018

@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.

@AlpacaGoesCrazy
Copy link

@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

@iansu
Copy link
Contributor

iansu commented Dec 11, 2018

This has been fixed but hasn't been released yet: #5573

@lock lock bot locked and limited conversation to collaborators Jan 8, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests