Hey, folks! I think you’ve read a lot of topics on how to use SVG in React.
The most popular approach is to use @svgr/webpack that allows you to import SVG as ReactComponent
. If you create projects using awesome
create-react-app you already use this package.
Usually you do it this way, right?
import React from 'react';
import { ReactComponent as Icon } from 'path/to/icon.svg';
function Icon() {
return <Icon />;
}
It works perfectly, BUT what if you want to create re-usable components to render your icons.
Let’s say, designer provides you a set of 50+ svg icons, how would you deal with them?
Many tutorials suggest to create components for each icon — it is straightforward, but tiresome way. Not to mention it produces a lot of boilerplate code.😖
If you have curiosity how to avoid useless boilerplating — welcome to my post.
I’ll present you a **simple, but **effective* way to manage SVG icons in your React App.*
TL;DR
If you are too impatient to get the answer, it is okay.
All you need to start using this elegant solution is a code snippet provided below.
import React, { useEffect, useState } from "react";
function Icon(props) {
const { name, ...otherProps } = props;
/* Use state hook to store icon module value */
const [iconModule, setIconModule] = useState(null);
useEffect(() => {
/* Use dynamic import to get corresponding icon as a module */
import(`./icons/${name}.svg`)
.then((module) => {
/* Persist data in state */
setIconModule(module);
})
.catch((error) => {
/* Do not forget to handle errors */
console.error(`Icon with name: ${name} not found!`);
});
}, [ name /* update on name change */ ]);
const renderIcon = () => {
if (!iconModule) return null;
/* Equal to: import { ReactComponent as Icon } from "./path/to/icon.svg" */
const Component = iconModule.ReactComponent;
return <Component {...otherProps} />;
};
return <>{renderIcon()}</>;
}
export default Icon;
Or you can play with it in codesandbox.io/s/how-to-use-svg-icon-sets-in-react:
How it works
Let’s dive into this approach together and go through it line by line.
It is simpler than it looks.
First, we create a new functional component that takes one required name
prop.
function Icon({ name, ...otherProps }) {
// implementation
}
Next, we use useState
hook to store icon component value with null
as initial value:
const [iconModule, setIconModule] = useState(null);
That’s where the magic happens✨…
We use dynamic expression in import
to get icon depending on provided name
. import
returns Promise
which resolves with module on success or rejects Erorr if icon not found.
Here we work with webpack bundling.
import(
./icons/${name}.svg)
will cause every .svg
file in the ./icons
directory to be bundled into the new chunk. At run time, when the variable name has been computed, any file like star.svg
will be available for consumption. You can read more about this here.
import(`./icons/${name}.svg`).then((module) => {
/* Persist data in state */
setIconModule(module);
}).catch((error) => {
/* Do not forget to handle errors */
console.error(`Icon with name: ${name} not found!`);
});
Finally, we should render our icon component if it is successfully imported. Webpack applies @svgr/webpack loader
on dynamic import: import(
./icons/${name}.svg)
same way it does for
static one: import Icon from “./path/to/icon.svg”
const renderIcon = () => {
if (!iconModule) return null;
/**
* Equal to:
* import { ReactComponent as Icon } from "./path/to/icon.svg";
*/
const Component = iconModule.ReactComponent;
return <Component {...otherProps} />;
};
That’s it guys 🎉.
Hope you enjoyed article and going to apply new knowledge for your next app. Feel free to comment and discuss it!
Top comments (3)
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of
Icon
.i think you are using next... i faced the same Error.
i installed package svgr react-svgr.com/docs/next/ by instruction and then i did this (it is different ^ but same idea to use svg file name as prop)
iconModule
always remainsnull
to me. Do you have any idea what it might be? Thanks