React Native: Artifacts in SVGs, why they happen and how to fix them
Table of contents
I just spent my whole Saturday afternoon plus a Sunday morning trying to render multiple SVGs properly in the same React Native screen, so I'm writing this article both to my future self and for others not to waste as much time as I did.
Context
As far as I can tell, the correct way to render local SVG files in React Native is to use react-native-svg-transformer
to transform SVG files into React components. The transformer uses svgr
and, optionally, svgo
for optimizations.
I followed react-native-svg-transformer
's instruction and require()
d the desired SVGs, but they were rendered incorrectly:
Note the artifacts in the Spanish, Portuguese and Vatican flags.
I thought this was very weird because I had also rendered these SVGs in a website before and they didn't suffer from the same issue.
One thing that I later learned was important is that these SVGs have id
s that are unique within themselves, but not unique globally. Apparently this wasn't a problem when rendering SVGs in a website because I was rendering them using an image
tag, which doesn't "leak" the IDs.
This means that an SVG rendered through an image
tag cannot reference another SVG's ID. However, react-native-svg-transformer
transforms the SVG into a React component, which means that the SVGs are inlined, so their IDs "leak". In that case, IDs must be globally unique, otherwise, depending on the order that SVGs render, some of their tags may reference IDs from other SVGs, which was the cause for the weird rendering.
Solution
It seems the solution is that each SVG must have IDs that are globally unique and different from other SVGs' IDs. However, I had hundreds of SVGs with similar IDs and I didn't want to manually update them.
Fortunately, react-native-svg-transformer
uses svgr
, which optionally uses svgo
, so I managed to use svgo
to generate sequential ID prefixes so the final IDs no longer conflict.
I managed to do it by creating the following file in the root of the project:
// .svgrrc.js
let count = 0;
module.exports = {
svgo: true,
svgoConfig: {
plugins: [{
name: "prefixIds",
params: {
prefix() {
return count++;
}
}
}],
}
};
This will prefix all IDs with a number followed by two underscores and suffixed by the original ID. If the first SVG had a tag with an ID of a
, the resulting React component will contain the ID 0__a
.
This fixed the artifacts that were being shown before:
Finally the SVGs are rendering properly!
Bonus Hint
Something else that I faced was that SVGs were not respecting their parent's size. The fix was to add a viewbox
property with 0 0 <width> <height>
and the SVGs stopped overflowing their parent.
Thank you for reading! This post is a bit different from the others, but I wanted to write it so I can reference it in case I ever deal with this problem again. Hopefully, it helps you too.
Have a nice one!