1

I made a menu component that accepts a json object with all menu itens. For icons i use react-icons/io. The json object is something like this:

const menus = {
    Item1: { buttonText: 'Item 1 text', icon: { IoLogoAndroid }, path: 'item 1 path' },
    Item2: { buttonText: 'Item 2 text', icon: { IoLogoAndroid }, path: 'item 2 path'},
};

This is the Menu function that will render the menu items as buttons:

const buttons = Object.keys(this.props.menus).map((menu) => {
        return (
            <a key={menu} href={this.props.menus[menu].path}
                onClick={this.changeMenu.bind(this, menu)}>
                {...this.props.menus[menu].icon} <- fail here
                {this.props.menus[menu].buttonText}
            </a>
        );
    }) 

I tried many ways to render the icon, but i am clueless on how this could work. Not sure if this is even possible. Any pointers?

1
  • I would like to know why this question gets 2 downvotes. Doesn't 3 people offer they help, providing 3 solutions to the problem in 1 day, is not enough to qualified as a valid question? If you are one of who downvote can you clarify so i don't make the same mistake again? Commented Mar 22, 2019 at 17:26

4 Answers 4

2

If you are importing the icon from where you are defining the object then just tag it <IoLogoAndroid/>;, so react knows it should treat it as an element to render.

const menus = {
    Item1: { buttonText: 'Item 1 text', icon: <IoLogoAndroid/> , path: 'item 1 path' },
    Item2: { buttonText: 'Item 2 text', icon: <IoLogoAndroid/>, path: 'item 2 path'},
};

And then just call it directly (remove the ...)

<a key={menu} href={this.props.menus[menu].path}
    onClick={this.changeMenu.bind(this, menu)}>
    {this.props.menus[menu].icon}
    {this.props.menus[menu].buttonText}
</a>

Alternatively you could just call React.createElement if you don't want to tag it in your object definition.

<a key={menu} href={this.props.menus[menu].path}
    onClick={this.changeMenu.bind(this, menu)}>
    {React.createElement(this.props.menus[menu].icon)}
    {this.props.menus[menu].buttonText}
</a>

Here is a sample showing the 2 implementations https://codesandbox.io/s/pmvyyo33o0

Sign up to request clarification or add additional context in comments.

4 Comments

Yes this is also a possible solution, however no reason to access the array once again, since each item, is already being mapped
I tried that. it breaks with: 'Objects are not valid as a React child (found: object with keys {IoLogoAndroid}). If you meant to render a collection of children, use an array instead.'. The second way gives the error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
Sorry I didn't adapt the definition of the menus object. The icons shouldn't be a object, it should be icon: IoLogoAndroid or icon: <IoLogoAndroid/> remove the {...} from the icon value. I edited my answer.
it work. Passing in json as tag works. But calling React.createElement still gives the same error. I may doing something wrong, it is my first react project. Anyway you solved. Tks
2

I was just working with a similar project, and I managed to make it work, with a syntax like this

I here have an array of objects (like yours)

links: [
      {
      name: 'Frontend',  
      link: 'https://github.com/Thomas-Rosenkrans-Vestergaard/ca-3-web', 
      icon: <FaCode size={40} />,
      id: 1
      }, 
      {
      name: 'Backend',
      link: 'https://github.com/Thomas-Rosenkrans-Vestergaard/ca-3-backend',
      icon: <FaCogs size={40}/>,
      id: 2
      },
      {
      name: 'Mobile',
      link: 'https://github.com/Thomas-Rosenkrans-Vestergaard/ca-3-app',
      icon: <FaMobile size={40} />,
      id: 3
       }] 

I then render my component by mapping, where I pass in the entire object as a prop

const projects = this.state.projects.map((project, i) => {
  return(
 <Project key={`Project key: ${i}`} project={project}  />
  )
})

I then use objectdestructing to get the prop

 const { logo } = this.props.project

then it can just be displayed

//in my case I use antd framework, so I pass the FAIcon component in as a prop 

 <Meta
    avatar={logo}
    title={title}
    description={description}
  />

I suppose you could do the same thing, by just passing the entire menu object in as a prop, and then accessing the icon?

Comments

1

You need to change the following:

icon: <IoLogoAndroid />

And in the code (remove the spread operator):

this.props.menus[menu].icon

Also, a few refactoring suggestions.

  • Do you really need to have an object of objects? Why not an array of objects, where every item has a "name" prop? It will be easier to iterate through, as you can access props directly from map, unlike with object keys.
  • You are creating a button list, so you should have ul and li tags aswell.
  • Consider passing only a reference to onClick such as:

    onClick={this.changeMenu}
    

If you need to pass data around, you should use dataset for that. Pass a name/path then find it inside the change handler to avoid rebinding inside every re-render.

Refactored suggestion with an array of objects

changeMenu = e => {
    const { menus } = this.props;
    const { menuName } = e.target.dataset;
    const menu = menus.find(menu => menu.name === menuName);
    // Do something with menu
    return;
  };

  renderMenu() {
    return (
      <ul>
        {this.props.menus.map(menu => (
          <li key={menu.name} style={{ listStyle: "none" }}>
            <a
              data-menu-name={menu.name}
              href={menu.path}
              onClick={this.changeMenu}
            >
              {menu.icon}
              {menu.buttonText}
            </a>
          </li>
        ))}
      </ul>
    );
  }

1 Comment

No problem. Glad it helped you.
0
import {createElement} from "react";
import IconDocumentText from "@/components/icons/IconDocumentText";

const SomeBlock = () => {
    const icons = {
        input: IconDocumentText,
    }

    return(
        <>
            {createElement(icons.input, {className:'w-4 h-4'})}
        </>
    }

}

export default SomeBlock;


---

IconDocumentText:

export default function IconDocumentText({className="w-6 h-6"}) {
    return (
        <svg className={className} ...></svg>
    )
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.