Avatar

Nǐ hǎo, I'm Julia.

Rendering Variable React Components

#react

4 min read

I was trying to figure out if there was an efficient way of sharing return components a couple of weeks ago. This statement probably doesn't mean much, so let me try to explain this through a coded example.

Let's say I have a constant array of objects that looks like this. (I'll get to what Bird, Cat and Dog are in a second.)

export const ANIMAL_CHARACTERS = [
  {
    nickname: 'Woody',
    scientificName: 'Picidae',
    name: 'Woodpecker',
    description: Bird,
  },
  {
    nickname: 'Hedwig',
    scientificName: 'Strigiformes',
    description: Bird,
  },
  {
    nickname: 'Garfield',
    scientificName: 'Felis catus',
    description: Cat,
  },
  {
    nickname: 'Snoopy',
    scientificName: 'Canis lupus familiaris',
    description: Dog,
  },
]

My aim is to then render a filtered selection of this array depending on some logic, let's say, a specific user's favourite animal characters.

{
  ANIMAL_CHARACTERS.filter(({ nickname }) => usersFavCharacters.includes(nickname)).map(
    ({ nickname, scientificName, description }) => {
      // say we get back Hedwig and Snoopy objects here
    },
  )
}

In my case, I needed to pass this information into a custom Accordion component I had built, which takes in a content prop, which, let's say, needs to be a string. At this point, the simplest thing to do would've been to just have the description values be strings, rather than a component. i.e. something like this, rather than what I had defined above.

export const ANIMAL_CHARACTERS = [
  {
    nickname: 'Woody',
    scientificName: 'Picidae',
    name: 'Woodpecker',
    description: 'A bird has wings and can fly.',
  },
  {
    nickname: 'Hedwig',
    scientificName: 'Strigiformes',
    description: 'A bird has wings and can fly.',
  },
  {
    nickname: 'Garfield',
    scientificName: 'Felis catus',
    description: 'A cat has 4 legs and meows.',
  },
  {
    nickname: 'Snoopy',
    scientificName: 'Canis lupus familiaris',
    description: 'A dog has 4 legs and barks.',
  },
]

This would work if you literally wanted a simple string to be rendered for the description, but in my case, the description for each of my elements was actually significantly longer and needed to be CSS styled in a particular way. The length of my array constant was also significantly longer, which meant there would be a lot more duplication if say there were 5 dogs, 10 cats, and 20 birds in my array.

The logical thing for me to do was therefore to package up each type of description into its own reusable component, which meant allowing it to have its own custom styling and content (e.g. say if I also wanted to include images). This brings us back to my original definition... but why aren't the description values written as components like <Bird /> rather than Bird?

export const ANIMAL_CHARACTERS = [
  {
    nickname: 'Woody',
    scientificName: 'Picidae',
    name: 'Woodpecker',
    description: Bird,
  },
  {
    nickname: 'Hedwig',
    scientificName: 'Strigiformes',
    description: Bird,
  },
  {
    nickname: 'Garfield',
    scientificName: 'Felis catus',
    description: Cat,
  },
  {
    nickname: 'Snoopy',
    scientificName: 'Canis lupus familiaris',
    description: Dog,
  },
]

It's because I need these description components to be rendered dynamically, depending on whether our logic determines if its parent element needs to be shown (in our example, whether it is in a specific user's favourite character array).

{
  ANIMAL_CHARACTERS.filter(({ nickname }) => usersFavCharacters.includes(nickname)).map(
    ({ nickname, scientificName, description }) => {
      return (
        <Accordion
          key={nickname}
          title={scientificName}
          content={/* we need to pass in the description component here */}
        />
      )
    },
  )
}

Passing in the component directly will not work here, so what you need to do instead is to first set a new const which acts as the variable component. You can then pass that variable component in as the content prop.

{
  ANIMAL_CHARACTERS.filter(({ nickname }) => usersFavCharacters.includes(nickname)).map(
    ({ nickname, scientificName, description }) => {
      const DescriptionComponent = description
      return <Accordion key={number} title={scientificName} content={<DescriptionComponent />} />
    },
  )
}

The gotcha here is that the const name needs to start with a capital letter for React to know that this will be a component. i.e. setting const descriptionComponent = description will not work.

© 2016-2024 Julia Tan · Powered by Next JS.