Levi D.
Levi D.

Reputation: 306

Render component object inside array dynamically based on props

This should be pretty simple but keep giving me error:

Here is my array:

const section = [
      {
        trigger: {
          title: 'Data',
          icon: <Icon />,
          editIcon: <Edit onClick={e => console.log(e)} />,
        },
        component: (
          <ListOne
          />
        ),
      },
      {
        trigger: {
          title: 'OTHER PERSONAL DATA',
          icon: <Like />,
          editIcon: <Edit onClick={e => this.goToOtherPersonalData(e)} />,
        },
        component: (
          <ListTwo
          }
          />
        ),
      },
      ...
]

What i wanna do is display some component from the array based on a boolean prop like this:

const section = [
      propIsTrue && {
        trigger: {
          title: 'Data',
          icon: <Icon />,
          editIcon: <Edit onClick={e => console.log(e)} />,
        },
        component: (
          <ListOne
          />
        ),
      },
      {
        trigger: {
          title: 'OTHER PERSONAL DATA',
          icon: <Like />,
          editIcon: <Edit onClick={e => this.goToOtherPersonalData(e)} />,
        },
        component: (
          <ListTwo
          }
          />
        ),
      },
      ...
]

But doesn't seems to be the correct syntax and give me error:

Uncaught TypeError: Cannot read properties of undefined (reading 'onClick')

So my answer is: Which is the correct way to dynamically display components based on passed prop?

Thanks in advance

Upvotes: 0

Views: 42

Answers (2)

3limin4t0r
3limin4t0r

Reputation: 21110

The main issue is probably propIsTrue && { object definition } within array context. Normally you'll see this within JSX context eg.

return (
  <div id="container">
    {propIsTrue && (
      <OnlyVisibleIfPropIsTrue />
    )}
  </div>
);

The reason this works in JSX is because null, undefined, false, are ignored when rendering.

The above JSX is compiled into the following JavaScript.

return (
  React.createElement("div", { id: "container" },
    propIsTrue && (
      React.createElement(OnlyVisibleIfPropIsTrue)
    )
  )
);

// assuming propIsTrue = false this becomes
return (
  React.createElement("div", { id: "container" },
    false // <- ignored by the renderer
  )
);

For a standard array definition this is not the case. It does work as long as propIsTrue is truthy.

// assuming propIsTrue = true
const people = [
  propIsTrue && { name: "John Doe" },
  { name: "Jane Doe" },
];

people //=> [{ name: "John Doe" }, { name: "Jane Doe" }]

But does not "skip" the definition of the first element if propIsTrue is falsy.

// assuming propIsTrue = false
const people = [
  propIsTrue && { name: "John Doe" },
  { name: "Jane Doe" },
];

people //=> [false, { name: "Jane Doe" }]

Like you can see the people array still has length 2, with the first element now being false.


Solution #1 - fix the array

If you want conditional array elements you could use a normal if-statement.

// assuming propIsTrue = false
const people = [];
if (propIsTrue) people.push({ name: "John Doe" });
people.push({ name: "Jane Doe" });

people //=> [{ name: "Jane Doe" }]

The above will create an people array with just Jane Doe and without the false value.

Another option would be to remove the falsy values after creation.

// helper
const truthy = (item) => !!item;

// assuming propIsTrue = false
const people = [
  propIsTrue && { name: "John Doe" },
  { name: "Jane Doe" },
].filter(truthy);

people //=> [{ name: "Jane Doe" }]

Solution #2 - account for falsy values in the array

If you have control over the JSX that renders your array, you could use your current solution, but you'll have to skip renders for falsy values.

// assuming propIsTrue = false
const people = [
  propIsTrue && { name: "John Doe" },
  { name: "Jane Doe" },
];

// ...

return (
  <ul>
    {people.map((person) => person && ( // <- notice the `person &&`
      <li>{person.name}</li>
    ))}
  </ul>
);

Upvotes: 1

Jae
Jae

Reputation: 556

Conditional Rendering would be the enouth solution for this case.

{propIsTrue ? 
  <ListOne ... />
  :
  <ListTwo ... />
}

If you need <Icon />, <Edit /> or whatever compoent in your <List />, just give it as a prop or you can implement with Component composition.

Upvotes: 0

Related Questions