Dynamic SVG component in Vite + React + TS
Amit Mondal
Posted on September 7, 2022
After hearing many amazing things about vite I decided to try it and create something and I am not at all disappointed with the DX. So when I was developing this fun project of mine (which is currently WIP) I thought of creating a dynamic SVG component that would load up just by giving the SVG icon name with all the SVG customizable attributes under my control.
The first hurdle was to find a plugin that can transform SVG to React component just like we have in CRA. After a few google searches landed on vite-plugin-svgr.
Install vite-plugin-svgr:
If you are using CRA for your React project then this step can be skipped as it configures SVGR(SVGR lets you transform SVGs into React components) under the hood for you.
To install the plugin run:
yarn add -D vite-plugin-svgr
Setting up vite-plugin-svgr:
Register the installed plugin in the vite config file, easy peasy!
File: vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr"; // transform svg to react component
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), svgr()], // register the plugin
});
Writing the custom hook:
Once SVGR setup is done we are ready to create the functionality that will grab the icon-name and fetch it from assets and load the same. We can also modify the hook so that it can take the icon's path and load it. From this hook, we export the loading state, error state, and the component itself.
File: useDynamicSvgImport.ts
import React, { useEffect, useRef, useState } from "react";
export function useDynamicSvgImport(iconName: string) {
const importedIconRef = useRef<React.FC<React.SVGProps<SVGElement>>>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<unknown>();
useEffect(() => {
setLoading(true);
// dynamically import the mentioned svg icon name in props
const importSvgIcon = async (): Promise<void> => {
// please make sure all your svg icons are placed in the same directory
// if we want that part to be configurable then instead of iconName we will send iconPath as prop
try {
importedIconRef.current = (
await import(`../../assets/icons/${iconName}.svg`)
).ReactComponent; // svgr provides ReactComponent for given svg path
} catch (err) {
setError(err);
console.error(err);
} finally {
setLoading(false);
}
};
importSvgIcon();
}, [iconName]);
return { error, loading, SvgIcon: importedIconRef.current };
}
Writing the component:
Let's write the wrapper component for our SVG, main aim of this wrapper is to provide the loading shimmer when the icon is not present to be displayed and apply some wrapper style and SVG attributes.
File: SvgIcon.tsx
import { useDynamicSvgImport } from "./useDynamicSvgImport";
interface IProps {
iconName: string;
wrapperStyle?: string;
svgProp?: React.SVGProps<SVGSVGElement>;
}
function SvgIcon(props: IProps) {
const { iconName, wrapperStyle, svgProp } = props;
const { loading, SvgIcon } = useDynamicSvgImport(iconName);
return (
<>
{loading && (
<div className="rounded-full bg-slate-400 animate-pulse h-8 w-8"></div>
)}
{SvgIcon && (
<div className={wrapperStyle}>
<SvgIcon {...svgProp} />
</div>
)}
</>
);
}
export default SvgIcon;
Using component:
Now the component is ready to be used with all the SVG element attributes and wrapper styles that can be passed on as a prop.
File: App.tsx
import './App.css';
import SvgIcon from './SvgIcon';
function App() {
return (
<div>
<h1>Hello!</h1>
<SvgIcon
iconName="react"
svgProp={{ width: 100, height: 100, fill: "#61dafb" }}
/>
<SvgIcon iconName="react" svgProp={{ stroke: "white", fill: "blue" }} />
</div>
);
}
export default App;
Working example link:
Do let me know if there are any better ways in the comments below.
Thanks for your time reading. Drop a few ❤️ or 🦄 if you liked it.
Posted on September 7, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.