Skip to content

Instantly share code, notes, and snippets.

@mikeyamadeo
Last active September 4, 2022 02:43
Show Gist options
  • Save mikeyamadeo/6bdbbfde7ff0e1c3cf3c to your computer and use it in GitHub Desktop.
Save mikeyamadeo/6bdbbfde7ff0e1c3cf3c to your computer and use it in GitHub Desktop.
Different approaches to dynamically rendering components with JSX

###Intro I wanted to use a facade pattern to provide a simple api for rendering job information in different forms using a Job component that received two props:

  • the job data
  • how to render the job
import React from 'react'
import Job from 'JobComponent'

const JobRenderTypes = React.createClass({ 
  render () {
    const jobData = {
      title: "Looking for help",
      employerId: "uniqueId",
    }
    return (
      <div>
        <Job data={jobData} renderAs="listing" />
        <Job data={jobData} renderAs="heading" />
        <Job data={jobData} renderAs="card" />
      </div>
    )
  }
})

A naive first approach at this Job facade looked like this:

import React from 'react'
import createJob from 'JobFactory'
import JobListing from './components/org.JobListing'
import JobHeading from './components/org.JobHeading'
import JobCard from './components/org.JobCard'

// well this is repetitive :/.... 
const renderAs = {
  listing (data) {
    return <JobListing data={data} />
  },
  heading(data) {
    return <JobHeading data={data} />
  },
  card (data) {
    return <JobCard data={data} />
  },
}

const Job = React.createClass({

  render () {
    const job = createJob(this.props.data)
    const renderFn = renderAs[this.props.renderAs]
    const content = renderFn ? renderFn(data) : ''

    return (
      <div className="Job">
        {content}
      </div>
    )
  }
})

export default Job

While returning jsx for each type of render was repetetive, I wasn't sure how to make the jsx dynamic. i.e. this doesn't work:

render() {
  const types = {
    listing: JobListing,
    heading: JobHeading,
    card: JobCard
  }
  const component = types[this.props.renderAs]
  return (<component data={jobData} />)
}

After looking into it I discovered accessing the property of an object does work. This seems to force jsx to recognize it as a variable:

render () {
  const types = {
    listing: JobListing,
    heading: JobHeading,
    card: JobCard
  }
  let render = {
   me: types[this.props.renderAs]
  }
  return (<render.me data={jobData} />)
}

Playing around a bit more yielded the discovery of a quirk of jsx. Variables beginning with non-alphabetic characters force jsx to treat it as a variable and use the variable's value rather than using the literal variable name.

render () {
  const types = {
    listing: JobListing,
    heading: JobHeading,
    card: JobCard
  }
  const component = types[this.props.renderAs] // this one will NOT work
  const $component = types[this.props.renderAs] // this one WILL
  return (
    <div>
      <component data={jobData} />
      <$component data={jobData} />
    </div
  )
}

Here is a look at what is happening when the jsx comes out the other end as plain ol' js:

render () {
  const types = {
    listing: JobListing,
    heading: JobHeading,
    card: JobCard
  }
  let component = types[this.props.renderAs] 
  let $component = types[this.props.renderAs] 
  
  // Notice it passing in "component" as a string vs $component as a variable
  return (
    React.createElement("div", null, 
  	  React.createElement("component", null), 
      React.createElement($component, null)
  ))
}

Here is the final product:

import './style'
import React from 'react'
import createJob from 'JobFactory'
import listing from './components/org.JobListing'
import heading from './components/org.JobHeading'
import details from './components/org.JobDetails'
import card from './components/org.JobCard'

const { object, string } = React.PropTypes
const DEFAULT_RENDER_TYPE = 'listing'
const renderAs = {
  listing,
  heading,
  details,
  card
}

const Job = React.createClass({

  propTypes: {
    data: object.isRequired,
    renderAs: string
  },

  /**
   * 1. Non-Alphabetic chars like '$' force JSX to recognize the variable as
   * a variable and use the variable value rather than the literal variable
   * name as a string.
   */
  render () {

    const job = createJob(this.props.data)
    const renderType = this.props.renderAs.toLowerCase() || DEFAULT_RENDER_TYPE
    const $component = renderAs[renderType] /* [1] */

    return (
      <div className="Job">
        <$component data={job} />
      </div>
    )
  }
  
})

export default Job
@romansorin
Copy link

Good work, been looking for something like this for dynamic element rendering through Firebase. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment