Inline SVGs in React + Webpack
| Megan PearsonSVGs are awesome. They are super sharp and scalable. They can be manipulated by CSS and/or JavaScript. They're accessible to screen readers and assisitve devices. They're particularly cool and fun and easy to work with when you combine them with reusable React components.
I created a method to do SVG icons in a single React component like this...
<Icon name="arrow" fill="pink" />
<Icon name="unicorn" fill="purple" />
because I wanted to...
- leverage the benefits of inline SVGs with the reusability of React components
- easily style and modify SVGs on the fly
- avoid having large chunks of SVG code directly in my React components
- avoid having to import a bunch of SVGs in various components all the time
- reuse the same component for all of my icons
- be able to take updated files from designers and just drop them in instead of having to make a bunch of edits
The Process
I started off by making the SVG itself a component (ex: <ArrowIcon />
) that I
could pass properties to in order to style it inline. For example, I had an
arrow icon
that I wanted to reuse and change its color on the fly (maybe to neon-yellow
)
by doing:
<ArrowIcon fill="#BCC614" />
This is great, but what if we want to use a different icon (say, a unicorn
)
in our Dashboard component? It's pretty redundant to make a whole component,
exactly like <ArrowIcon />
, just to change the icon.
So! I made my reusable <ArrowIcon />
more flexible by also passing in a name
property. This would correlate to the name of the SVG and be used to pick the
right icon. For example:
<Icon name="arrow" fill="#00ABB0" />
<Icon name="accepted" fill="#E89B5B" />
<Icon name="unicorn" fill="#B05BE8" />
Gotchas
There are some ramifications to this method. Most importantly, because we load all of the SVGs into one component, there can be some issues if you have implemented code splitting. This could cause the SVGs to be duplicated across bundles. It's probably fine if there's just a handful of small SVGs, but if there are a lot and they're big - it could bloat your code. Therefore, this method is best suited for small to medium sized projects.
Additionally, if there are fill (or other) attributes in the SVG files, they may take precedence due to specficity. Therefore, you might need to remove them from the SVG itself and account for them in the component instead.
This method will load all of the SVGs regardless if they are in use or not. Consider if this is acceptable for your project.
Make sure the SVGs are properly loaded
Install the react-svg-loader and configure in webpack.config.js. For example:
{
module: {
rules: [
...,
{
test: /\.svg$/,
use: [{
loader: 'react-svg-loader',
}],
},
...,
]
}
}
Make a component for just ONE icon
Create a functional component that just returns the arrow icon with a property called "fill". This is what will allow us to change the icons' color from our other components. In this example I have set a default color (an exciting, neon-green) if nothing is specified from the other components.
import React from 'react';
import Arrow from './arrow.svg';
const ArrowIcon = ({fill = '#5BE8AE'}) => {
return <Arrow fill={fill} />;
};
export default ArrowIcon;
Ta da! This is your reusable, inline SVG component. You can use it in any other
component by importing it and change its color by setting a fill property. For
example, if we want an arrow icon in our <Dashboard />
component we would use
it as follows:
import React from 'react';
import ArrowIcon from '../ArrowIcon';
const Dashboard = () => {
<div>
Some text with an arrow next to it <ArrowIcon fill="#666" />
</div>;
};
export default Dashboard;
Woo! Inline SVG component! But, now we want to use that unicorn icon
.
So, instead of making an entirely different component just to change the icon,
let's modify our existing <ArrowIcon />
to be more flexible and reusable for
any icon.
Make a component for ANY icon
To make the existing <ArrowIcon />
more flexible, let's start by renaming it
to <Icon />
. Then we will modify it as follows:
import React from 'react';
import PropTypes from 'prop-types';
/* import all of the icons that you want to use */
import Arrow from './arrow.svg';
import Accepted from './accepted.svg';
import Rejected from './rejected.svg';
import Unicorn from './unicorn.svg';
/* create a function that will pick which icon to use */
const pickIcon = name => {
switch (name) {
case 'arrow':
return Arrow;
case 'accepted':
return Accepted;
case 'rejected':
return Rejected;
case 'unicorn':
return Unicorn;
default:
throw new Error('no SVG for: ' + name);
}
};
/* pass the name & fill props (that we will specify in our
other components) to Icon to pick the right icon */
const Icon = ({name, fill = '#5BE8AE'}) => {
const SVG = pickIcon(name);
return <SVG fill={fill} />;
};
/* set the propTypes so we can catch bugs with typechecking */
Icon.propTypes = {
name: PropTypes.string.isRequired,
fill: PropTypes.string,
};
export default Icon;
Beautiful. Now we can specify which icon we want as a property in our components
and style it as well. For example, back in <Dashboard/>
:
import React from 'react';
import Icon from '../Icon';
const Dashboard = () => {
<div>
<span>
Text with an arrow <Icon name="arrow" fill="#666" />
</span>
<span>
Here is our majestic <Icon name="unicorn" />
</span>
</div>;
};
export default Dashboard;
There we have it.. a reusable, self-contained, inline SVG React component. :)