react-icons
react-icons copied to clipboard
how make dynamic icons
Describe the problems How make dynamic icons?
Minimal sample repository
import { lazy } from 'react';
const DynamicIcon = ({...props}) => {
const iconDir = props.icon?.slice(0, 2).toLowerCase()
console.log(iconDir) // ri, tn, tb
// expetation -> import { RiShieldStarLine } from "react-icons/ri"
const Icon = lazy(() => import(`react-icons/${iconDir}/index.js`).then(icons => icons[props.icon]))
console.log(Icon) // error Cannot find module
return (
<div>
<Icon />
</div>
)
}
export default DynamicIcon
Expected behavior
i want to call dynamic icon from API.
like with props 'RiShieldStarLine'.
How to call like static import { RiShieldStarLine } from "react-icons/ri"
with React.lazy
Errors
I got error
`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.`
`Error: Cannot find module '/frontend/node_modules/react-icons/vs/index.js' imported from /frontend/build/index.js`
Desktop (please complete the following information):
- Device: Macbook Pro 2020
- OS: Monterey 12.5
- Browser: Google Chrome
- Version: 104.0.5112.101
Hello, this is my code for this purpose! I created a component in React called Icon. In it I pass the name of the icon, as "AiFillAlert" and from there it does all the necessary process. You can pass the original properties of the icon through the propsIcon attribute.
// File "Icon.tsx"
import loadable from "@loadable/component"
import { IconBaseProps, IconType } from "react-icons/lib"
interface typesPropsIcon {
nameIcon: string;
propsIcon?: IconBaseProps
}
export function Icon({ nameIcon, propsIcon }: typesPropsIcon): JSX.Element {
const lib = nameIcon.replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(" ")[0].toLocaleLowerCase();
const ElementIcon: IconType = loadable(() => import(`react-icons/${lib}/index.js`), {
resolveComponent: (el: JSX.Element) => el[nameIcon as keyof JSX.Element]
});
return <ElementIcon {...propsIcon} />
}
# File "App.tsx"
import { Icon } from "./Icon";
export default function App() {
return <Icon nameIcon="AiFillDownSquare" propsIcon={{ size: 20 }} />
}
i hope it helps :) Whatever suggestion I'm at will!
thanks for your share code @obaraujo . I will use it later on next commit. thanks a lot 🥳
Thanks for the solution @obaraujo .
However, icons from Ionicons 5
throws error as it requires io5
library. So, for handling the exception people can use 1st icon of that module.
resolveComponent: (el) => el[nameIcon] != null ? el[nameIcon] : el[Object.keys(el['default'])[0]]
Hello, this is my code for this purpose! I created a component in React called Icon. In it I pass the name of the icon, as "AiFillAlert" and from there it does all the necessary process. You can pass the original properties of the icon through the propsIcon attribute.
// File "Icon.tsx" import loadable from "@loadable/component" import { IconBaseProps, IconType } from "react-icons/lib" interface typesPropsIcon { nameIcon: string; propsIcon?: IconBaseProps } export function Icon({ nameIcon, propsIcon }: typesPropsIcon): JSX.Element { const lib = nameIcon.replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(" ")[0].toLocaleLowerCase(); const ElementIcon: IconType = loadable(() => import(`react-icons/${lib}/index.js`), { resolveComponent: (el: JSX.Element) => el[nameIcon as keyof JSX.Element] }); return <ElementIcon {...propsIcon} /> }
# File "App.tsx" import { Icon } from "./Icon"; export default function App() { return <Icon nameIcon="AiFillDownSquare" propsIcon={{ size: 20 }} /> }
i hope it helps :) Whatever suggestion I'm at will!
This Works well, with the exception of having a bloated bundle. Tried the same dynamic import with "@react-icons/all-files" and that freezes my build. Any help with this will be appreciated.
Tried using the code from: @obaraujo in Typescript but I'm quite unsure how to properly type this. Thanks again! :
const ElementIcon: IconType = loadable(() => import(`react-icons/${lib}/index.js`), {
resolveComponent: (el: JSX.Element) => el[nameIcon as keyof JSX.Element]
});
Currently, it works for me, but I have to annotate it with // @ts-expect-error
It looks like:
import loadable from "@loadable/component";
import { IconBaseProps, IconType } from "react-icons/lib";
interface typesPropsIcon {
nameIcon: string;
propsIcon?: IconBaseProps;
}
export function Icon({ nameIcon, propsIcon }: typesPropsIcon): JSX.Element {
const lib = nameIcon
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
.split(" ")[0]
.toLocaleLowerCase();
// @ts-expect-error
const ElementIcon: IconType = loadable(async () => await import(`react-icons/${lib}/index.js`), {
resolveComponent: (el: JSX.Element) => el[nameIcon as keyof JSX.Element],
});
return <ElementIcon {...propsIcon} />;
}
Note that I'm using the following: dependencies:
"@loadable/component": "5.15.2"
"react-icons": "4.7.1"
devDependencies:
"@types/loadable__component": "5.13.4"
Any help on properly typing this is very much appreciated.
Hello, this is my code for this purpose! I created a component in React called Icon. In it I pass the name of the icon, as "AiFillAlert" and from there it does all the necessary process. You can pass the original properties of the icon through the propsIcon attribute.
// File "Icon.tsx" import loadable from "@loadable/component" import { IconBaseProps, IconType } from "react-icons/lib" interface typesPropsIcon { nameIcon: string; propsIcon?: IconBaseProps } export function Icon({ nameIcon, propsIcon }: typesPropsIcon): JSX.Element { const lib = nameIcon.replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(" ")[0].toLocaleLowerCase(); const ElementIcon: IconType = loadable(() => import(`react-icons/${lib}/index.js`), { resolveComponent: (el: JSX.Element) => el[nameIcon as keyof JSX.Element] }); return <ElementIcon {...propsIcon} /> }
# File "App.tsx" import { Icon } from "./Icon"; export default function App() { return <Icon nameIcon="AiFillDownSquare" propsIcon={{ size: 20 }} /> }
i hope it helps :) Whatever suggestion I'm at will!
I cannot get this to work as I get the following error: TypeError: Failed to resolve module specifier 'react-icons/fa/index.js'. Does anyone have an idea how to solve this problem? Thanks!
Thanks for this lil snippet!
I wanted to use this in my React project that wasnt using Typscript yet, so here's a conversion for anyone else:
import loadable from "@loadable/component"; // remember to npm install me
export function GrabIcon({ nameIcon, propsIcon }) {
const lib = nameIcon.replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(" ")[0].toLowerCase();
const ElementIcon = loadable(() => import(`react-icons/${lib}/index.js`), {
resolveComponent: (el) => el[nameIcon]
});
return <ElementIcon {...propsIcon} />;
}
Edit: I noticed that when I built the project, I am now getting the warning The bundle size is significantly larger than recommended
, which is caused by @loadable/component library being imported, so this should be considered when applying this snippet!
Hi !
I'm facing the same problem, the code is working well but it's not a good practice at all :/ Nobody found a way to load dynamic icons efficiently ?
I'm using the react-icons wrapper for Strapi on backend and I need to show icon on frontend by the name.
Using the same code but getting the error
@obaraujo Any fix?
@akanshSirohi you might need the reference to the JSX element class/function instead of the rendered version. I had a similar error when trying to pass the dynamic icon as a property to some other component. If that's the case you should be able to solve it by simplifying the function so it just returns ElementIcon
(not <ElementIcon {...propsIcon} />
).
If you only need icons from one particular library there's an easier solution that doesn't require loadable
and doesn't cause any TypeScript errors. I use Phosphor Icons here but it should also work for others:
import * as PhosphorIcons from "react-icons/pi";
// Hardcoded the `Pi` prefix because it only uses Phosphor Icons anyway.
// Falls back to a question mark icon when the named icon doesn't exist.
const Icon = PhosphorIcons[`Pi${name}` as keyof typeof PhosphorIcons] || PhosphorIcons.PiQuestion;
I use it like this in a component that requires a reference to the element:
<MenuItem icon={Icon} />
You can also render it directly using <Icon />
with whatever properties you need.
It will load all Phosphor (or whatever you use) icons so your bundle size will be larger, but not as large as with the fully dynamic loadable
solution.
@copini thank you for your suggestions. However, I have already developed a functioning component. I am mentioning it here in case someone finds it helpful.
https://github.com/DanielPantle/strapi-plugin-react-icons/issues/22#issuecomment-1793514490
My Solution: Just import what you want
import { FiSettings } from "react-icons/fi";
import { HiHome } from "react-icons/hi";
import { HiUser } from "react-icons/hi2";
export const Icons = {
HiHome,
HiUser,
FiSettings,
};
and then
import { Icons } from "@/assets/icon/Icon";
import React from "react";
const LayoutSidebar = () => {
return (
<div className="h-screen w-16">{React.createElement(Icons["HiHome"])}</div>
);
};
export default LayoutSidebar;
the ""HiHome" is the dynamic value
Hello! Typical, but still rather interesting solution.
Below is same using js
import loadable from "@loadable/component"
import { IconBaseProps, IconType } from "react-icons/lib"
export default function Icon({ nameIcon, propsIcon }) {
const lib = nameIcon.replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(" ")[0].toLocaleLowerCase().trim();
const ElementIcon = loadable(() => import(`react-icons/${lib}`), {
resolveComponent: (el) => el[nameIcon]
});
return <ElementIcon {...propsIcon}/>;
}
@obaraujo this is my code based on what you shared :
import loadable from "@loadable/component";
const DynamicIcon = ({ iconName }) => {
const ElementIcon = loadable(() => import(`@heroicons/react/20/solid/${iconName}`));
return <ElementIcon />;
};
export default DynamicIcon;
but the problem is , it cannot read the icon based on the iconName , but if i pass the iconName hardcoded to the path like
const ElementIcon = loadable(() => import(`@heroicons/react/20/solid/UsersIcon`));
it works fine but it is not dynamic anymore
i use next.js and this is the error :
failed to compile
Module not found: Can't resolve '@heroicons/react/20/solid'
4 |
5 | const DynamicIcon = ({ iconName }) => {
> 6 | const ElementIcon = loadable(() => import(`@heroicons/react/20/solid/${iconName}`));
| ^
7 |
8 | return <ElementIcon />;
9 | };
https://nextjs.org/docs/messages/module-not-found
any idea ?