0

I am using the react-icons package in my react app. It works fine in the sense that you can import and use a font awesome icon in the following manner:

import { FaTag } from 'react-icons/fa';
...
<FaTag />

However, I want to be able to generate these icons within components that are more reusable. For example, support that I have a menu list and each item has an icon. That list is sent as an array where one entry is the title, one is the link address, and one is the name of the icon. With no dynamic method, you have to do this ...

const SidebarMenuItem:FC<propsObj> = ({title, link, icon}):ReactElement => {

  return (

    <li>
      <Link to={link}>
        <span>{title}</span>
        { (icon==="Tag") && <FaTag /> }
        { (icon==="QuestionCircle") && <FaQuestionCircle /> }
        { (icon==="UserAstrounaut") && <FaUserAstronaut /> }
        { (icon==="InfoCircle") && <FaInfoCircle /> }
        { (icon==="List") && <FaList /> }
        { (icon==="Cogs") && <FaCogs /> }
      
      </Link>

    </li>

  )
}

export default SidebarMenuItem;

I would like to be able to do this somewhat dynamically. I don't have a problem with a large import statement that takes in every icon I would need to use. What I would like to do after that is this ...

const SidebarMenuItem:FC<propsObj> = ({title, link, icon}):ReactElement => {

  const iconElement = React.createElement(icon, null);

  return (

    <li>
      <Link to={link}>
        <span>{title}</span>
        <div>{iconElement}</div>         
      </Link>
    </li>

  )
}

export default SidebarMenuItem;

This actually results in the proper component being rendered. However, no icon appears and there is no svg graphics with it. It appears that somewhere in the compilation process, the font awesome package renders the graphics before the create component is ran?

Is there any way around this?

Joshua Foxworth
  • 1,236
  • 1
  • 22
  • 50
  • If you have a JSON file or a data source with the page/components for your app could you make an enum class that maps the icons to the appropriate item and then call it via {IconEnum.title}. Would that work? – dan webb Jul 26 '21 at 02:36

3 Answers3

0

you could make an array of components like

import React from 'react'
import { FaTag, FaList } from 'react-icons/fa'

const components = {
    FaTag,
    FaList,
}

function SidebarMenuItem({title, link, icon}) {
    //given that icon contains 'Tag' or 'List'
    const Icon = components[`Fa${icon}`]
    return (
       <li>
         <Link to={link}>
           <span>{title}</span>
           <Icon />         
         </Link>
       </li>
    )
}
Kamran
  • 103
  • 5
0

I am guessing you are using a module bundler that intelligently only includes files that you have used in your code (like webpack). See tree shaking for more info about that.

If you just want to get around making a component that requires an icon, you could simply inject it via props:

const SidebarMenuItem:FC<propsObj> = ({title, link, IconComponent}):ReactElement => {
  return (
    <li>
      <Link to={link}>
        <span>{title}</span>
        <IconComponent />        
      </Link>
    </li>

  )
}

export default SidebarMenuItem;

// use this like so:
import { FaTag } from 'react-icons/fa';
//... somewhere in render
<SidebarMenuItem title="the title" link="http://google.com" icon={FaTag} />

This does not get rid of the import statements but since IDEs like vscode will insert the import automatically when you are coding (if configured correctly), I do not see this as a big deal. Also it enables your module bundler to actually only include the icons you used.

Taxel
  • 3,859
  • 1
  • 18
  • 40
0

import { BsArrowLeftCircle } from 'react-icons/bs'

First import icons then put them as a src.
const [open, setOpen] = useState(false)

const Menus = [
        { title: 'Dashboard', paht: '/dashboard', src: <BsArrowLeftCircle /> },
        { title: 'Course', paht: '/course', src: <BsArrowLeftCircle /> },
        { title: 'Profile', paht: '/profile', src: <BsArrowLeftCircle /> },
        { title: 'Signin', paht: '/login', src: <BsArrowLeftCircle />, gap: 'true' },
    ]

Then you can loop through them like this. You will likely need to enclose icon inside span. You can ignore the tailwind styling.

<ul className='pt-6'>
            {Menus.map((menu, index) => (
                <li
                    key={index}
                    className={`flex items-center gap-x-4 p-3 text-base font-normal rounded-lg cursor-pointer dark:text-white hover:bg-gray-200 dark:hover:bg-gray-700
                    ${menu.gap ? 'mt-9' : 'mt-2'} ${index === 0 && 'bg-gray-200'}`}
                >
                    <span className='text-2xl'>{menu.src}</span>
                    <span className={`${!open && 'hidden'} origin-left duration-300`}>
                        {menu.title}
                    </span>
                </li>
            ))}
        </ul>
Idrs
  • 11
  • 2