A Reactful Way to Create Dynamic Components

Posted by Niky Morgan on February 27, 2019

As an engineer and former instructor at Flatiron School, I’ve built around 100 React applications for lecture, for fun and for production use. The vast majority of these applications share a common starting point: fetching data from an API, getting an array of results, storing that array of results in state and mapping it into an array of components.

It is that same old React pattern, but having worked on production applications, I know how easy it is for a large-scale React application to expand exponentially. For example, you might start with a shared component that is utilized in a few different places, but when each instance of it requires different event handlers and validations, you start to feel like you are building brittle workarounds in order to keep some semblance of DRYness. So you split one shared component into multiple separate components. How do you make this call? When is it worth breaking a component into multiple components and how do you reconcile that with maintaining DRYness? When making these choices, how do React design conventions factor in?

One trick that can serve you in building a more DRY yet Reactful application is rendering dynamic React components. Let’s say we are building a wiki for starships and their personnel. When our app loads, we might fetch crew member data that looks something like this:

crew member data Crew Member Data Sorry, I meant this data:

[
  {
    name: 'Jean-Luc Picard',
    position: 'Captain',
    ship: 'USS Enterprise (NCC-1701-D)'
  },{
    name: 'Geordi La Forge',
    position: 'Chief Engineering Officer',
    ship: 'USS Enterprise (NCC-1701-D)'
  },{
    name: 'Beverly Crusher',
    position: 'Chief Medical Officer',
    ship: 'USS Enterprise (NCC-1701-D)'
  }
]

Once we have our data, we would save it into state and use it to render information about our crew.

//CrewMember.js
const CrewMember = ({name, position, ship}) => (
  <li>
    <h3>{name}</h3>
    <p>{position} of the {ship}</p>
  </li>
)
//CrewContainer.js
class CrewContainer extends Component {
  state = { crew: [] }

  componentDidMount() {
    // fetch content from your api and set it into state
  }
  render() {
    const { crew } = this.state
    return (
      <ul>
        { crew.map(member => <CrewMember {member} />) }
      </ul>
     )
   }
}

With our one-line map and small functional CrewMember component, we start to wonder if we even need a CrewContainer and CrewMember component or if we could just render all this within the parent. Applications never stay this simple for long, though.

What if our data is actually more complex? Surely each position within the crew requires its own skillset, so our data may reflect that. Captains might be rated on their calm under pressure. Doctors might be rated on their bedside manner. Engineers might have a secret number they multiply all their time estimates by so they can appear to deliver faster than promised.

// data fetched from API
[
  {
    name: 'James Tiberius Kirk',
    position: 'Captain',
    ship: 'USS Enterprise (NCC-1701)',
    calmUnderPressureRating: 'cool as a cucumber'
  },{
    name: 'Montgomery Scott',
    position: 'Chief Engineering Officer',
    ship: 'USS Enterprise (NCC-1701)',
    timeMultiplier: 4
  },{
    name: 'Leonard McCoy',
    position: 'Chief Medical Officer',
    ship: 'USS Enterprise (NCC-1701)',
    bedsideManner: 'sardonic bordering on hostile'
  }
]
// CrewMember.js
const CrewMember = ({
  name,
  position,
  ship,
  calmUnderPressureRating,
  timeMultiplier,
  bedsideManner
}) => (
  <li>
    <h3>{name}</h3>
    <p>{position} of the {ship}</p>
    { calmUnderPressureRating &&
      <p>I am as {calmUnderPressureRating} </p> }
    { timeMultiplier &&
      <p>That will take roughly {timeMultiplier} times longer than you think, but I'll do it in half.</p>}
    { bedsideManner &&
      <p>I am {bedsideManner} </p> }
  </li>
)

With all this specialized information, our component is starting to get bogged down in prop-checking. If calmUnderPressure is being passed down, render it. If timeMultiplier is being passed down, render it. If bedsideManner is being passed down, render that. This is an appropriate time to consider breaking out our crew members into separate components. If we decide to add ten officers with new positions to our data, our CrewMember component will quickly become too large. Attempting to keep our code repetition-free by creating one component with a dozen conditional branches makes it complicated and difficult to read.

Instead, we can create and export multiple crew member components differentiated by position. Our crew member components are separated by concern and don’t check whether they need to render multiple different props. Each component knows what props it receives instead and how to display them.

// CrewMember.js
const Captain = ({name, position, ship, calmUnderPressureRating}) => (
  <li>
    <h3>{name}</h3>
    <p>{position} of the {ship}</p>
    <p>I am as {calmUnderPressureRating} </p>
  </li>
)
const Engineer = ({name, position, ship, timeMultiplier}) => (
  <li>
    <h3>{name}</h3>
    <p>{position} of the {ship}</p>
    <p>That will take roughly {timeMultiplier} times longer than you think, but I'll do it in half.</p>
  </li>
)
const Doctor = ({name, position, ship, bedsideManner}) => (
  <li>
    <h3>{name}</h3>
    <p>{position} of the {ship}</p>
    <p>I am {bedsideManner}.</p>
  </li>
)

While our crew components have become much simpler, our CrewContainer has more work to do. Instead of simply mapping one crew member object to create one CrewMember component, we need to be able to pick the correct component for each crew object. One solution is to conditionally check what the position prop is and return the matching component.

// CrewContainer.js
const CrewSelector = (props) => {
  switch(props.position) {
    case 'Chief Engineering Officer':
      return <Engineer {props} />
    case 'Chief Medical Officer':
      return <Doctor {props} />
    case 'Captain':
      return <Captain {props} />
  }
}
class CrewContainer extends Component {
  state = { crew: [] }
  componentDidMount() {
    // fetch content from your api and set it into state
  }
  render() {
    const { crew } = this.state
    return(
      <ul>
        { crew.map(member => <li><CrewSelector {member} /></li>) }
      </ul>
    )
  }
}

Now when we map over the crew array, we are executing a CrewSelector function which examines the position prop of the crew member object and returns a component based on it. This code certainly works, but it feels like we just moved the conditional checks from our CrewMember component to a new CrewSelector component.

What is wrong with this conditional statement? It just doesn’t feel like it takes advantage of the benefits of React. React was designed with a declarative API. To quote the React docs:

You tell React what state you want the UI to be in, and it makes sure the DOM matches that state.

It is a subtle difference, but by using a case statement we are giving more explicit instructions: if the position is captain, render us a captain component. Imagine the difference between giving someone step by step directions to get to a location versus giving them an address to arrive at.

So in this case, what is the equivalent of giving someone just an address with no directions? We let our props determine our presentation. Instead of giving React step-by-step instructions, we give it information about the end result we want and let it get there using state and props.

// CrewContainer.js
const components = {
  'Chief Engineering Officer': Engineer,
  'Captain': Captain,
  'Chief Medical Officer': Doctor
}
const CrewSelector = (props) => {
  const MatchingComponent = components[props.position]
  return <MatchingComponent {props} />
}
class CrewContainer extends Component {
  state = { crew: [] }
  componentDidMount() {
    // fetch content from your api and set it into state
  }
  render() {
    const { crew } = this.state
    return(
      <ul>
      { crew.map(member => <li><CrewSelector {member} /></li>) }
      </ul>
    )
  }
}

Now instead of having a case statement explicitly choose our components, we have an object that maps position name to component name. Our CrewSelector function uses the position prop to determine the matching crew component from the object.

Though the difference is slight, using this more declarative method of dynamically rendering crew components based on position is the most Reactful pattern. It keeps our code clear and easy to read even though we have multiple functional crew member components.

captain picard Picard, Captain Jean-Luc. Loves Risa and React.

This post was featured on the Flatiron School Technology Team blog. Check out our team blog for cool posts on React, Elixir and more!