Last year, we wrote about camel casing inline SVG props in React, why inline SVGs are a good performance win in the browser, and how to avoid annoying browser warnings when converting an SVG image to a React component.
Since then, we've learned of a couple more cases that make converting SVG images to React components annoying to deal with. I recommend reading our "Automated camel cased props for React" blog post linked above for more context on the motivation for automating these cases before reading this post.
Let's get right into all of the cases and how we've automated them in the new eslint-plugin-svg-jsx package.
- Changing dashed cased attributes into camel cased props (e.g.
fill-rule="value"
becomesfillRule="value"
)* - Changing colon cased attributes into camel cased props (e.g.
xmlns:xlink="value"
becomesxmlnsXlink="value"
) - Changing style strings to style props (e.g.
style="margin: 20px"
becomesstyle={{ margin: '20px' }}
)
*Camel casing dashed cased attributes works the same as it did in eslint-plugin-react-camel-case
package we released last year.
Dashed cased attributes
This was the original motivation for creating an ESLint plugin to recognize and fix issues with inline SVGs. Since React is actually JavaScript and not HTML, camel casing props is easier to process. It's the same reason when accessing a key in JS object, you prefer using the syntax myObj.camelCasedKey
instead of the syntax myObj['snake-cased-key']
. React as a library just enforces camelCasing to avoid any ambiguity on this topic.
One interesting case about React's camel casing is the className
prop. This one isn't to avoid dashs in the React prop because the original HTML attribute is simply class
. The reasoning is that class
is a reserved keyword in JavaScript, so React needed a way to differentiate the prop from keyword class
.
Enough historical context - dashed cased attributes (same thing as snake-cased attributes) are bad news in React land! The new eslint-plugin-svg-jsx
will find and automatically fix those attributes to turn them into proper camel cased props. Since this was the original case from the camel casing inline SVG props in React blog post, I recommend reading that for more context if you'd like to (not needed, just read the get started section and profit right now 🤑)
Colon cased attributes
The first of the new cases added is colon-cased attributes in SVGs. In SVG/HTML/XML, colons denote a namespace. They are out of scope for this post, besides me saying it's really ironic that React requires us to camel case them, just so React can then un-camel case them when actually writing to the DOM. Is it dumb we have to do this? Absolutely. Is React and all of it's benefits worth this small dumb convention? Also absolutely!
Where colon cased props break down in React is with embedded PNGs within SVGs. Here's an example of an SVG with a PNG embedded inside of it:
<svg
width="423"
height="882"
viewBox="0 0 423 882"
fill="none"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<pattern
id="pattern0"
patternContentUnits="objectBoundingBox"
width="1"
height="1"
>
<use
xlink:href="#image0_1834_10713"
transform="scale(0.000483092 0.000724638)"
/>
</pattern>
<image
id="image0_1834_10713"
width="2070"
height="1380"
xlink:href="data:image/png;base64,dahsdsahdasuu..."
/>
</defs>
</svg>
Base 64'd PNG inside an SVG element, with the id of "image0_1834_10713"
While this is valid HTML, it will cause a big problem if you move it to React without camel casing the prop: React won't find the image you are referencing inside the pattern
element. The element <use xlink:href="#image0_1834_10713" transform="scale(0.000483092 0.000724638)" />
will have a reference to the image element with id image0_1834_10713
, but no way to actually find it! Which means... no image will be shown to the user, and your asset will be horribly incorrect.
Instead of catching this by hand when you set up your inline SVG, eslint-plugin-svg-jsx
will recognize this incorrect prop and camel case it for you. The correct, React version of the SVG:
export default (props) => (
<svg
width="423"
height="882"
viewBox="0 0 423 882"
fill="none"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
{...props}
>
<defs>
<pattern
id="pattern0"
patternContentUnits="objectBoundingBox"
width="1"
height="1"
>
<use
xlinkHref="#image0_1834_10713"
transform="scale(0.000483092 0.000724638)"
/>
</pattern>
<image
id="image0_1834_10713"
width="2070"
height="1380"
xlinkHref="data:image/png;base64,dahsdsahdasuu..."
/>
</defs>
</svg>
)
Camel cased props (xlinkHref
) compatible with React elements instead of colon cased attributes
With the above, React-compatible SVG component, your UI will be stellar and with great performance ⚡️
Style strings
The last case of the 3 is style strings. In my opinion, this one is the most annoying case because React won't even work if you get this wrong - a build error will occur, and the flow state you were in during development comes to a screeching halt.
One of the benefits of inline SVG over using them with an img tag is that you have full control over styling and attaching event listeners to child tags of SVGs... but that also means they can come with the style
attribute already set. For example:
<mask
id="mask0_1834_10713"
style="mask-type: alpha"
mask-units="userSpaceOnUse"
x="48"
y="87"
width="50"
height="50"
>
<circle cx="72.8218" cy="111.822" r="24.8218" fill="#D9D9D9" />
</mask>
Not ideal. The mask
element on our SVG comes with a builtin style via the style
attribute, but React style
props need to be an object.
Inline style attributes like the above are considered bad practice in web development as they make targeting elements with CSS difficult and can produce unexpected behavior. But when you export an SVG from a tool like Figma, they can often appear directly on a mask like this because the mask element is unique to SVGs, making it essentially non-targetable by most CSS.
In any case, inline style props in React are actually much more common because of the component-based nature of React. Elements in React are already broken up by logical boundaries (e.g. being an element in a component), so inline styles will apply to all instances of the component/element they are used on. For the purposes of this blog post: all we need to do is convert the string style attribute on the SVG into an object for the React element's style prop. eslint-plugin-svg-jsx
handles that as well!
<mask
id="mask0_1834_10713"
style={{ maskType: 'alpha' }}
maskUnits="userSpaceOnUse"
x="48"
y="87"
width="50"
height="50"
>
<circle cx="72.8218" cy="111.822" r="24.8218" fill="#D9D9D9" />
</mask>
Ideal! The style prop has been converted to an object, making React happy which makes us happy 😊
Get started
- Add the dependency:
yarn add -D eslint-plugin-svg-jsx
ornpm install --save-dev eslint-plugin-svg-jsx
- In your .eslintrc.js:
- Add
svg-jsx
to your plugins - Add the
svg-jsx
rules:
'svg-jsx/camel-case-dash': 'error', 'svg-jsx/camel-case-colon': 'error', 'svg-jsx/no-style-string': 'error',
- Add
Your final .eslintrc.js should look something like:
module.exports = {
parser: '@babel/eslint-parser',
extends: ['standard', 'standard-jsx', 'plugin:prettier/recommended'],
plugins: ['no-only-tests', 'prettier', 'svg-jsx'],
rules: {
'svg-jsx/camel-case-dash': 'error',
'svg-jsx/camel-case-colon': 'error',
'svg-jsx/no-style-string': 'error',
},
}
Added svg-jsx
to the plugins array and all the svg-jsx
rules to the rules object
A few things of note
.eslintrc.js
is just one of many file formats to use with ESLint. We prefer JS since that is the language we use and is more inherently customizable than something like json
or yml
, but use the config format you prefer.
The 'automated' part of this plugin comes from ESLint's fix errors feature. Your IDE will give you the option to fix it while you are editing code, or you can run eslint with the --fix
option, e.g. eslint src --fix
will automatically fix these issues for you in all JS files within the src
directory.
At Anvil, we have taken the automation a step further by introducing pre-commit hooks to Git, so we ensure we are always checking in code that has been linted properly. I recommend this article to see how you can also set up pre-commit hooks in your project.
Time to build some beautiful UIs
Now that you recognize some of the annoying cases with inline SVGs in React, time to forget them and use eslint-plugin-svg-jsx
. I've personally saved days of work and many headaches from using the plugin over the course of a few months. I hope you'll find as much use out of the plugin as I have, so let's get back to building the web in a fast & fun way! If you have any other interesting cases you think the plugin should cover, we'd love to hear from you at developers@useanvil.com, or feel free to contribute in the GitHub repo.