React, dynamically importing SVG’s
First of all: credit for this solution should go to user dev_junwen at Stackoverflow for this excellent answer: https://stackoverflow.com/a/61472427/1505415
Rendering SVG’s on a website can be made in a couple of different ways. I will be focusing on a particular way it can be done in React, which gives us some neat advantages.
Here’s what I want to achieve:
- Import all SVG’s from a folder at runtime, i.e based on props
- Render the SVG as a React component, as in <MySvg />
❌ Importing the components dynamically removes the need to do a manual import on all our SVG’s like:
import carIcon from ‘./icons/car.svg’;
import wheelIcon from ‘./icons/wheel.svg’;
import engineIcon from ‘./icons/engine.svg’;
Why not render as <img> tag ?
One common question is why to not just render them as
<img src=”path/to/icon.svg” />
The reason I want to render as <MySvg /> instead is that I can pass in some important props, fill, width and height, which isn’t valid properties on a img tag. This will let us control the size and fill color of the SVG, which is a common use case for, let’s say, an icon library.
✅ So, here’s all the magic:
import React, { useState, useEffect, useRef } from 'react';const Icon = ({ name, size=16, fill="#000" }) => {
const ImportedIconRef = useRef(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
const importIcon = async () => {
try {
const { default: namedImport } = await import(`./icons/${name}.svg`);
ImportedIconRef.current = namedImport;
} catch (err) {
throw err;
} finally {
setLoading(false);
}
};
importIcon();
}, [name]);
if (!loading && ImportedIconRef.current) {
const { current: ImportedIcon } = ImportedIconRef;
return <ImportedIcon width={size} height={size} fill={color} />;
}
return null;
}; } catch (err) {
throw err;
} finally {
setLoading(false);
}
};
importIcon();
}, [name]);
if (!loading && ImportedIconRef.current) {
const { current: ImportedIcon } = ImportedIconRef;
return <ImportedIcon width={size} height={size} fill={color} />;
}
return null;
};
Basically, we make use of two important things here, webpack’s import function, and React’s useRef function, but we also import a non default exported module, which is not very common and is what made this tricky for me to understand from the beginning.
When our component receives its props, it looks for an update in the name prop, and runs the importIcon function, which imports the SVG file, extracts the component from it and assigns it to our ref. The weird part here is the default
, which is easy to miss.
Let’s say we would only import the file and call it esModule, then put a debugger right after and have a look at esModule
, this is what we have:
So we get an object which has adefault
property, which is a function that returns a component. In this case the component is Econ
, because the name we passed in is “econ” —so we are trying to import a “econ.svg”.
The difference here from a more common import MyComponent from './components/MyComponent'
, is that our module is not default exported, as in export default MyComponent
, and so we need to destructure and rename the “default” key (because default is a reserved keyword) from the returned object in order to actually render it. The way we do that is:
const { default: namedImport } = await import(`./icons/${name}.svg`)
This is what makes it possible to render the imported SVG as a component. Now we can easily render our SVGs without manual imports!
<Icon name=”car” size={35} color=”#ccc” />
🍻And that’s it! Hope it helped. If you liked this article, please give me one of those clapping hands, and if you hated this article, let me know in the comments so I can get better! :)
PS:
I’m building a tool for generating B2B leads in the UK, if you are looking to automate your lead generation process in the most simple way possible, check out leadsfinder.co.uk
— Eric
Twitter: @ercintheloft