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:
- 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'
- Create a registry of supported
icons
from those.
const icons = {
faStackOverflow,
faTwitter
}
- 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"
}]
*/
- 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)
- Render profiles, passing each
IconComponent
to<FontAwesomeIcon />
fromreact-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.