2

I'm reading from a JS object (from JSX) and trying to pass value of a component but it's rendered as string.

I tried placing components (in icon key of data see below) in {} but that doesn't help as data gives an error.

Here's the simplified version of the files.

data.js as below:

const data = [
 {
    title: "some title",
    desc: "some desc",
 },
 [
   {
     icon: "<TwitterIcon />",
     title: "title 1",
     desc: "desc 1",
   },
   {
     icon: "<FacebookIcon />",
     title: "title 2",
     desc: "desc 2",
   },
 ],
]

export { data }

index.js that reads data object and passes as props to AnotherComponent:

import { data } from "../path/to/data"
import AnotherComponent from "../path/to/AnotherComponent"

const Homepage = () => {

  return (
    <AnotherComponent {...data} />
  )
}

AnotherComponent.jsx as below:

import {TwitterIcon, FacebookIcon} from "../path/to/CustomIcons"

const AnotherComponent = ({ ...data}) => {

  return (
    {data[1].map(item => (
      <div>{item.icon}</div> // this prints string as opposed to rendering the component
      <div>{item.title}</div>
      <div>{item.desc}</div>
    ))}
  )
}

index.js returns:

<div><TwitterIcon /></div>
<div>title 1</div>
<div>desc 1</div>
<div><FacebookIcon /></div>
<div>title 2</div>
<div>desc 2</div>
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
megatower
  • 33
  • 6
  • 3
    JSX is not an HTML string. Wrap the data in some function called within the render cycle and render the icon directly in the object: `icon: ,` – Emile Bergeron Nov 03 '20 at 14:54
  • If you do not control the data, see [how to render custom JSX strings](https://stackoverflow.com/q/64191372/1218980), though it depends on the use-case and just fixing the design of the app might fix the problem without a custom solution. – Emile Bergeron Nov 03 '20 at 15:00
  • Nikita wasn't wrong (in their deleted answer), spreading an array on a JSX element will use all indexes as props, which are used as object keys, destructured in the component, etc, which is code smell. – Emile Bergeron Nov 03 '20 at 15:05

2 Answers2

3

In the object you are defining as:

{
  icon: "<TwitterIcon />",
  title: "title 1",
  desc: "desc 1",
}

Don't use "<TwitterIcon />" It will always return a string, instead use TwitterIcon:

{
  icon: TwitterIcon,
  title: "title 1",
  desc: "desc 1",
}

And finally, call it where you need it, in this way:

const AnotherComponent = ({ ...data}) => {

  return (
    {data[1].map(item => (
      <div><item.icon /></div> // HERE: check I'm calling item.icon as React Component
      <div>{item.title}</div>
      <div>{item.desc}</div>
    ))}
  )
}

In this way you are passing the icon to anywhere you want and not just passing a string. So, you can call it as a Component when you need it to render. I do it a lot in my work.

Hector
  • 56
  • 1
  • 3
0

I think you should pass directly the icon component in the object, like this:

const data = [
{
    title: "some title",
    desc: "some desc",
 },
 [
   {
     icon: <TwitterIcon />,
     title: "title 1",
     desc: "desc 1",
   } ...

Then in index.js you can do (it is more clear to pass down props like this):

const Homepage = () => {
  return (
    <AnotherComponent data={data} />
  )
}

In AnotherComponent.jsx now you can do:

const AnotherComponent = ({data}) => {
  return (
    {data[1].map(item => (
      <div>{item.icon}</div>
      <div>{item.title}</div>
      <div>{item.desc}</div>
    ))}
  )
}
the_previ
  • 683
  • 6
  • 12
  • While this will look like it works, the `` lifecycle might be messed up as it's not created and updated in the app, but rather outside of it. A way to make it work well is to wrap the `data` in a function which returns a new array each time the calling component is rendered. – Emile Bergeron Nov 03 '20 at 15:15
  • Sounds better. You can call that function on useEffect – the_previ Nov 03 '20 at 15:23