Home > Software design >  Display an icon based on a prop passed as a string - React
Display an icon based on a prop passed as a string - React

Time:08-08

I'm trying to display different icons based on a prop (coming from a string stored in a database per user). I do not want to pass the icon itself as a prop - as the information about what Icon to show is stored in a database.

My parent component fetches data and gets a something I've called "displayIcon" as a string. This is passed on as a prop to the "AppCard" and depending on the value of the displayIcon, I'd like to render different icons.

One way to do it is using a ternary operator - but it does not feel very scalable, elegant or efficient.

Any suggestions?

How I Pass the data:

 return (
    <BaseLayout title={"Processes"}>
      {userRole == "stakeholder" && (
        <AppSection sectionName={"Add Processes"}>
          <div className="appLayout">
            <AppCard
              title={"Add process"}
              navigation={"/processes/addnew"}
              displayIcon={"FaPlus"}
            />
          </div>
        </AppSection>
      )}

  );

AppCard Component

// import { FaQuestion } from "react-icons/fa";
import Link from "next/link";
import {
  FaPlus,
  FaSitemap,
  FaQuestion,
  FaRegListAlt,
  FaUser,
} from "react-icons/fa";
import {
  HiOutlineUserGroup,
  HiCog,
  HiDocumentText,
  HiOutlineFire,
  HiAcademicCap,
  HiLightBulb,
  HiScale,
  HiOutlineChartPie,
  HiOutlineFlag,
  HiOutlineGlobe,
} from "react-icons/hi";

function AppCard({title, navigation, displayIcon }) {
  return (
    <Link
      href={{
        // pathname: `/admin/edit/${navigation}`,
        pathname: navigation,
      }}
    >
      <div className=" cursor-pointer flex flex-col items-center ">
        <div className="appView ">
        
          {displayIcon == "FaPlus" && <FaPlus size="44" />}
          {displayIcon == "FaUser" && <FaUser size="44" />}
          {displayIcon == "FaSitemap" && <FaSitemap size="44" />}
          {displayIcon == "FaQuestion" && <FaQuestion size="44" />}
          {displayIcon == "FaRegListAlt" && <FaRegListAlt size="44" />}
          {displayIcon == "HiOutlineUserGroup" && (
            <HiOutlineUserGroup size="44" />
          )}
          {displayIcon == "HiCog" && <HiCog size="44" />}
          {displayIcon == "HiDocumentText" && <HiDocumentText size="44" />}

          {displayIcon == "HiOutlineFire" && <HiOutlineFire size="44" />}
          {displayIcon == "HiAcademicCap" && <HiAcademicCap size="44" />}

          {displayIcon == "HiLightBulb" && <HiLightBulb size="44" />}
          {displayIcon == "HiScale" && <HiScale size="44" />}

          {displayIcon == "HiOutlineChartPie" && (
            <HiOutlineChartPie size="44" />
          )}
          {displayIcon == "HiOutlineFlag" && <HiOutlineFlag size="44" />}
          {displayIcon == "HiOutlineGlobe" && <HiOutlineGlobe size="44" />}

          
        </div>
        <p className="text-siteTheme-accentcolorDark font-psans  mt-1">
          {title}
        </p>
      </div>
    </Link>
  );
}

export default AppCard;

CodePudding user response:

Elegance is in the eye of the beholder. If it's working, then it's fine.

To improve elegancy you could store all as an object, in a const maybe, and render as needed.

const STRING_TO_ICON = {
 FaPlus: <FaPlus size="44" />,
 FaUser: <FaUser size="44" />},
 FaSitemap: <FaSitemap size="44" />,
 FaRegListAlt: (size = 44) => <FaRegListAlt size={size} /> // dynamic size
}
function AppCard({ title, navigation, displayIcon }){
  ...
  return(
    ...
     <div className="appView ">
      {STRING_TO_ICON[displayIcon]}
      {STRING_TO_ICON[displayIcon(32)]}
     </div>
    ...
  );
  ...
}

CodePudding user response:

I do what you are describing for the social profiles component on my personal website. The way I've implemented it is:

  1. Import all Font Awesome icons I'll support into the component. I'm sure this can be optimized in the future but I haven't explored that yet.

import {
  faStackOverflow,
  faTwitter
} from '@fortawesome/free-brands-svg-icons'

  1. Create a registry of supported icons from those.

const icons = {
  faStackOverflow,
  faTwitter
}

  1. Fetch my social profiles data. In it I define the icon, by key, I want to use for each profile. Those keys currently corresponds to the import name (e.g., 'faStackOverflow').

const profiles = useSocialProfiles()

/*

[{
  "icon": {
    "name": "stack-overflow",
    "reactIcon": "faStackOverflow"
  },
  "displayName": "StackOverflow"
}, {
  "icon": {
    "name": "twitter",
    "reactIcon": "faTwitter"
  },
  "displayName": "Twitter"
}]

*/

  1. Loop through all profiles and use the supported icon registry to map each icon key to a new IconComponent field. It contains the icon SVG.

const profilesToIcons = profile => {
  const { icon: { reactIcon } = {} } = profile
  return {
    IconComponent: icons[reactIcon],
    profile
  }
  
const profilesWithIcons = profiles.map(profilesToIcons)

  1. Render profiles, passing each IconComponent to <FontAwesomeIcon /> from react-fontawesome.

{profilesWithIcons.map(({ IconComponent }) => (
  <FontAwesomeIcon
    icon={IconComponent}
    sx={{ fontSize: [4, 5, 6] }}
  />
))}

My data source for the profiles was previously a database, but now I use a configuration file. The same implementation has worked OK for both. If you notice the number of icons – used or unused – increasing your client's bundle size past what is acceptable then you'll probably want to refactor or iterate on this to address that. There are probably a few ways to handle that issue, but I haven't personally arrived there yet. For now this has worked fine for my needs and the few icons I'm importing.

  • Related