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:

SVGs rendering with weird artifacts.

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 ids 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:

SVGs rendering properly.

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!