5

I am trying to output some svgs and output them from a list, here is my render method:

render() {

        const renderTag = () => {
            const Tag = this.props.id
            return(<Tag />)
        } 

        return (
            <div key={this.props.name} className="social-box">
                <a className={this.props.id + "-link"}> 
                    {renderTag()}
                </a>
            </div>
        )
    }

However, the DOM node is always lowercase i.e. <facebook> rather than <Facebook> this.props.id is correctly rendered to the console as Facebook. Can anyone tell me why react or the browser incorrectly renders as lowercase, and therefore not the component, and how to fix?

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
Mick Jones
  • 517
  • 4
  • 19
  • Nothing seems to be wrong with your code accept that you are declaring a function in render which will be recreated each time render is called. Can you produce a reproducible demo of your problem – Shubham Khatri Mar 12 '18 at 15:40
  • @ShubhamKhatri there is nothing wrong in doing that, how would that cause this issue? In fact see my answer, which is most probably the cause for this. – Sam Pettersson Mar 12 '18 at 15:42
  • @SamPettersson, when did I say that moving `renderTag` outside of `render` will solve the problem. I just asked the other person to provide a reproducible demo of his problem because even though React converts tags into lower case, the OP problem is that the component is not being rendered – Shubham Khatri Mar 12 '18 at 15:46

4 Answers4

3

It's a technical implementation of React, all tags get lowercased on this line here, AFAIK it's not possible to render non-lowercased tags and that is by design.

Read more here.

Sam Pettersson
  • 3,049
  • 6
  • 23
  • 37
1

i suggest that you would take a look at this article about dynamic components.

The most relevant example from the article:

import React, { Component } from 'react';
import FooComponent from './foo-component';
import BarComponent from './bar-component';
class MyComponent extends Component {
    components = {
        foo: FooComponent,
        bar: BarComponent
    };
    render() {
       const TagName = this.components[this.props.tag || 'foo'];
       return <TagName />
    }
}
export default MyComponent;

you most likely have a limited amount of components that could be rendered, so you might create a dictionary that contain a key (name of the component) to the component itself (as shown in the example) and just use it that way:

import Facebook from './FaceBook';
import Twitter from './Twitter';

const components = {
   facebook: Facebook,
   twitter: Twitter
};
    render() {
            return <div key={this.props.name} className="social-box">
                    <a className={this.props.id + "-link"}> 
                        <components[this.props.id] />
                    </a>                
</div>;

    }
Tom Mendelson
  • 625
  • 4
  • 10
  • I found this article before coming here as a last resort. I implemented this kind of approach, but it, and your code here didn't work. – Mick Jones Mar 13 '18 at 09:07
  • i created a test online app for this case: https://stackblitz.com/edit/react-fwfeez work as described above – Tom Mendelson Mar 13 '18 at 09:38
0

I find the answer eventually. @TomMendelson almost had the answer, but it needed fleshing out a bit more.

A function to create the component outside of the render method, suggested by @ShubhamKhatri actually did the job. Here's the final code:

import React from 'react';
import Facebook from './svg/Facebook';
import LinkedIn from './svg/LinkedIn';
import Twitter from './svg/Twitter';
import Pinterest from './svg/Pinterest';

class SocialMediaBox extends React.Component {

    renderElement(item) {
        const Components = {
            'Facebook': Facebook,
            'Twitter': Twitter,
            'Pinterest': Pinterest,
            'LinkedIn': LinkedIn 
        }
        return React.createElement(Components[item], item);
    }

    render() {

        const Element = this.renderElement(this.props.id)

        return 
        (
            <div>
                {Element}
            </div>
        )
    }
}

export default SocialMediaBox;
Mick Jones
  • 517
  • 4
  • 19
0

Thanks for the question and answers; alongside the answers given in Dynamic tag name in jsx and React they helped me to find a solution in my context (making a functional component in Gatsby with gatsby-plugin-react-svg installed):

import React from "react"
import FirstIcon from "../svgs/first-icon.inline.svg"
import SecondIcon from "../svgs/second-icon.inline.svg"
import ThirdIcon from "../svgs/third-icon.inline.svg"

const MyComponent = () => {
  const sections = [
    { heading: "First Section", icon: () => <FirstIcon /> },
    { heading: "Second Section", icon: () => <SecondIcon /> },
    { heading: "Third Section", icon: () => <ThirdIcon /> },
  ]

  return (
    <>
      {sections.map((item, index) => {
        const Icon = item.icon
        return (
          <section key={index}>
            <Icon />
            <h2>{item.heading}</h2>
          </section>
        )
      })}
    </>
  )
}

export default MyComponent

As mine is a Gatsby project I used the above mentioned plugin, but it itself process svgs with svg-react-loader so the basic principle should work in any React project using this package.

Mina
  • 150
  • 2
  • 12