Building an SVG Loader in React
In this guide, we will learn how to import SVGs as markup within our components that can be styled with CSS.
Recently I was working on a component that loads multiple SVGs from an API. I needed to style these SVGs within a component to match the colour pallet of the site, and I also needed to be able to switch between light and dark variants determined by state.
I looked at SVGR which can import SVG files as components, but it isn't really designed to load multiple SVGs dynamically.
I have spent a lot of time working on PHP projects and I have become accustomed to loading in SVG's using PHP's file_get_contents() function, which takes a file path as an arguments and returns the file contents as a string. I have always been a fan of this method for loading in SVG because it doesn't bloat your component with SVG markup while still allowing a method to easily style the SVG. So how can we do something similar in React?
#Building the SVG Contents loader.
We can leverage the Fetch API to load an SVG and get the contents as a string.
import DOMPurify from 'isomorphic-dompurify'
/**
* SVG Get Contents
*
* @param svgPath The path to the SVG file.
* @returns
*/
export async function svgGetContents(svgPath: string): Promise<string | null> {
try {
const res = await fetch(`/${svgPath}`)
if (!res.ok) {
console.error(`Failed to fetch SVG: ${res.statusText}`)
return null
}
const svg = await res.text()
const sanitised = DOMPurify.sanitize(svg)
return sanitised
} catch (err) {
console.error('Error loading SVG:', err)
return null
}
}
Let's break down the function:
- Establish an svgPath argument for our function, this can be either local or remote. Be mindful of where you are loading SVGs from as SVG can execute scripts and only load from trusted sources
- Create a try/catch block and attempt a fetch to an
svgPath - Next, we check the response status with
res.ok - If
res.okis true we get the entire response, in this case the SVG markup by using thetext()method, which returns a Promise that resolves to astring. As I mentioned above, SVGs can contain malicious code. To mitigate this we utilise DOMPurify to sanitise and remove potential nasty code. I'm using isomorphic-dompurify as it is already part of my application and works great client side and server side. - If
res.okreturnsfalsewe log an error to the console and exit the function withreturn null
- If
- Finally, we have our catch block that will log any errors it catches to the console. (In your own application, you can easily hook this into your own error reporting system.)
#Creating an Inline SVG Component
We can build a React component to render the SVG by injecting the SVG markup into a parent <figure> element using the aptly named dangerouslySetInnerHTML() which is a constant reminder of the XSS risks of unsanitised data.
import { useEffect, useState } from 'react'
import { svgGetContents } from '@scripts/svgGetContents'
export const InlineSVG: React.FC<{ svgPath: string }> = ({ svgPath }) => {
const [sanitisedSVG, setSanitisedSVG] = useState<string>('<span>Error loading SVG</span>')
useEffect(() => {
svgGetContents(svgPath)
.then((svgContent) => setSanitisedSVG(svgContent || '<span>Error loading SVG</span>'))
.catch((error) => console.error('Error fetching SVG:', error))
}, [svgPath])
return <figure dangerouslySetInnerHTML={{ __html: sanitisedSVG }} />
}Let's break down this component to determine how it works:
- Determine this is a client component with
use client - Import
useEffectanduseStateas we will be loading the SVG content as side effect within auseEffecthook - Define an
InlineSVG()functional component that takessvgPathas an argument. - Call
useStateto store oursanitisedSVGmarkup string and set theinitialStatevalue to an empty string - Set up a
useEffecthook to load our SVG as a side effect - Call svgGetContents() which returns a Promise.
- add a
.then()method to set the sanitised SVG string or return an error string - add a
.catch()method to handle errors and send aconsole.error()
- Return a figure element containing the SVG Markup, or an error message.
#SVG Styling
Now that we have a method to load in SVG's we can style the SVG elements from within a component.
#Brand Icons component
Here is a BrandIcons component, used to apply brand colours to a collection of SVG icons.
import { InlineSVG } from '../InlineSVG/InlineSVG'
export const BrandIcon = (props: { svgPaths: Array }) => {
return (
<div className="brand-icons">
svgPaths.map(iconPath, index) => {
return (
<div className="brand-icon">
<InlineSVG svgPath={props.iconPath} />
</div>
)
}
</div>
)
}#Brand Icon Styling
.brand-icon svg path {
fill: var(--brand-primary);
stroke: var(--brand-secondary);
}
#Conclusion
This article provides a method for loading SVGs Inline within components so that they can be styled with CSS. It's one of many ways that this can be achieved, but what I like about this approach is that it keeps our component markup clean, it's quick and the only dependency is DOMPurify
This example is a lightweight MVP, to provide an understanding of the technique, but it would be relatively easy to expand upon this for more complex SVG interactions and animations.