xabra
xabra

Reputation: 239

Rendering Material-UI icons from an array

Background info:
I'm using react and material-ui.
To keep the code clean, I populate menu items from a const array, like so:

const menuItems = [
  { label: "Home", path: "/home" },
  { label: "Accounts", path: "/accounts" },
  { label: "Organizations", path: "/organizations" },
];

Each item in the array is an object containing a label and a redirect path. I map over the items when rendering. Very basic.

Problem:
I would like to include a material-ui icon component in the menuItems array so the icon can be rendered next to the label. But I can't find a way to reference the icons by a name string
https://material-ui.com/components/material-icons/

Possible solutions:

  1. put the icon component into a string: { label: "Accounts", path: "/accounts" }, icon: "<AccountBox/>"} but then I somehow need to evaluate the string into jsx. I don't know how.

  2. Make a react functional component which renders a different icon depending on a prop, for example: <IconSwitch icon = {"accountIcon"} /> and hard-code different icons inside the RFC. Not pretty, but should work.

  3. Punt and use different icons such as svg icons or font icons that can referenced by a name string.

Any suggestions on how to do this? Thanks

Upvotes: 4

Views: 10343

Answers (3)

Mahe Pawar
Mahe Pawar

Reputation: 11

// I have better way to avoid all of this other hustle .

// 1: Make Every icon in Array which is in Jsx from to simple name.

// Ex:

[
  { Name: "New", Icon: <HomeIcon /> },
  { Name: "JS Mastery", Icon: <CodeIcon /> },
  { Name: "Coding", Icon: <CodeIcon /> },
  { Name: "ReactJS", Icon: <CodeIcon /> },
  { Name: "NextJS", Icon: <CodeIcon /> },
]


  to




   [
      ({ Name: "New", Icon: HomeIcon },
      { Name: "JS Mastery", Icon: CodeIcon },
      { Name: "Coding", Icon: CodeIcon },
      { Name: "ReactJS", Icon: CodeIcon })
    ];

// 2: Remember Using Object keys name as capital ,here:- "Name , Icon" not "name , icon".

// 3: Now Simply use : -

{
  categories.map(({ Name, Icon }) => (
    <button key={Name}>
      <span>{Name}</span>
      <span> {<Icon/>} </span> 
    </button>
  ));
}

//use icon in this cleaver way

Upvotes: 1

Zachary Haber
Zachary Haber

Reputation: 11027

Icon Font

You can use the Icon component. https://material-ui.com/components/icons/#icon-font-icons

To use an icon simply wrap the icon name (font ligature) with the Icon component, for example:

import Icon from '@material-ui/core/Icon';

<Icon>star</Icon>

https://codesandbox.io/s/material-demo-forked-sj66h?file=/demo.tsx

Assuming you set up your menu items with the appropriate icon ligatures:

  const menuItems = [
    { label: "Home", path: "/home", icon: "home" },
    { label: "Accounts", path: "/accounts", icon: "account_circle" },
    { label: "Organizations", path: "/organizations", icon: "settings" }
  ];

Then you can map over them:

      {menuItems.map(({ label, icon }) => {
        return (
          <span key={label}>
            {label} <Icon>{icon}</Icon>
          </span>
        );
      })}

SVG Icons

If you want to use SVG icons instead of basic icons, I'd recommend pulling only the SVG icons you plan to use in order to allow the icons you aren't using to be tree-shaken from the resulting bundle. The ability to tree shake is a good reason to use SVG icons over font icons.

import { Home, AccountCircle, Settings, AddCircle } from "@material-ui/icons";

If you want to allow user input of all icons or aren't aware ahead of time which icons will be displayed, you can import everything from @material-ui/icons as in Jonathan's answer.

If you aren't putting the list of icons into something that needs to be able to be stringified (i.e. Redux/sent through an API call) then you can just directly put the icons into the array and render them:

  const menuItems: MenuItem[] = [
    { label: "Home", path: "/home", icon: <Home /> },
    { label: "Accounts", path: "/accounts", icon: <AccountCircle /> },
    { label: "Organizations", path: "/organizations", icon: <Settings /> }
  ];

 // Rendering:

      {menuItems.map(({ label, icon }) => {
        return (
          <span key={label}>
            {label} {icon}
          </span>
        );
      })}

If you are going to put the Icons somewhere that needs to be stringified, the above won't work, so I'd recommend putting the icons you want to use into an object to map them. That way you have a string to icon map.

Example: https://codesandbox.io/s/material-icons-svg-udcv3?file=/demo.tsx

import { Home, AccountCircle, Settings, AddCircle } from "@material-ui/icons";

const icons = {
  Home,
  AccountCircle,
  Settings
};

In the case of the example above (i.e. rendering the icons from an array)

interface MenuItem {
  label: string;
  path: string;
  icon: keyof typeof icons;
}

  const menuItems: MenuItem[] = [
    { label: "Home", path: "/home", icon: "Home" },
    { label: "Accounts", path: "/accounts", icon: "AccountCircle" },
    { label: "Organizations", path: "/organizations", icon: "Settings" }
  ];

// Rendering:

      {menuItems.map(({ label, icon }) => {
        const Icon = icons[icon];
        return (
          <span key={label}>
            {label} <Icon />
          </span>
        );
      })}

Upvotes: 12

Jonathan
Jonathan

Reputation: 894

You can import all from @material-ui/icons and than create an Icon component dynamically:

import React from 'react'
import * as icons from '@material-ui/icons'

interface MenuItem {
    label: string,
    icon: keyof typeof icons,
    path: string
}

export function Menu() {

    const menuItems: MenuItem[] = [
        { label: 'Home', path: './home', icon: 'Home' },
        { label: 'Accounts', path: './accounts', icon: 'AccountCircle' },
        { label: 'Organizations', path: './organizations', icon: 'Settings' }
    ]

    return (
        <>
            {menuItems.map(menuItem => {
                const Icon = icons[menuItem.icon]

                return (
                    <span key={menuItem.path}>
                        {menuItem.label} <Icon />
                    </span>
                )

            })}
        </>
    )
}

Upvotes: 1

Related Questions