Injecting Dynamic Elements to Components

Introduction

Let’s say we’re working on an UI component library using React JS. We make a super awesome looking button, maybe even the best button in the world. But suddenly, our computer shuts off without saving our component! Like Tenacious D, we create a tribute to the button which looks like:

class AwesomeButton extends Component {
  render() {
    const { children, ...other } = this.props;

    return <button {...other}>{children}</button>;
  }
}

Everything is working great. We create buttons all over the place <AwesomeButton onClick={doStuff}>Click ME</AwesomeButton>.

The next day, the boss comes over, “This button is amazing, let’s make a button link to Google!” Easy enough, we create a new component that instead uses an anchor tag.

class AwesomeButtonLink extends Component {
  render() {
    const { children, ...other } = this.props;

    return <a {...other}>{children}</a>;
  }
}

Weeks later, another programmer walks over, “Hey, we’re converting to using React Router. Can we get a button that can use the Link component?” Mumbling under your breath, we create yet another Awesome component.

class AwesomeButtonReactRouterLink extends Component {
  render() {
    const { children, ...other } = this.props;

    return <Link {...other}>{children}</Link>;
  }
}

We have ourselves a problem. Every time there is a new request we have to create new components that are very similar, just using slightly different render elements. What if the company decides to re-brand. Instead of blue, we are a red company now. Little changes to the visuals of these AwesomeButton components need to be updated individually. Think DRY! Is there a better way of doing this? Stay tuned.

Dynamically Injecting Elements

What if the consumer of a component could define its base element? Let’s look at the example below:

class AwesomeButton extends React.Component {
  render() {
    const { children, tag = "button", ...other } = this.props;
    const Tag = tag;

    return <Tag {...other}>{children}</Tag>;
  }
}

So we have this new property called tag. Tag will be our way of passing in an element/component into AwesomeButton. If the es6 destructing looks different, let me explain. We will pull out the tag prop from this.props. If no value is defined for tag, we will set its default value to be "button" (A Button HTML element). The next line, const Tag = tag; is so we can fulfill what React’s JSX considers a component. All components must be uppercase, where html elements need to be lowercase. Since we are using then variable tag, JSX will always treat the incoming values as a component. So, we have to uppercase our variable. We now render the prop Tag as our element. Whatever tag equals, that will be our element!

A few examples:

<AwesomeButton onClick={doSomething}>Click Me!<AwesomeButton>

Will render the default button. <button onClick={doSomething}>Click Me!</button>

<AwesomeButton tag="a" href={`/myPage`}>Click Me!<AwesomeButton>

Will render using an anchor tag <a href={'/myPage'}>Click Me!</a>

<AwesomeButton tag={Link} to={`/myPage`}>Click Me!<AwesomeButton>

Will render using a React Router Link component <Link to={'/myPage'}>Click Me!</Link>

Neat! But why is this happening?

JSX Transformations

Our JSX, in the render function, gets transformed into plain jsx functions that the browsers can use. With this in mind, our AwesomeButton’s JSX gets transformed into a React.createElement() function with the element name as the first argument. With the help of our good friend Babel, let’s see what different components compile to!

First, let’s look at a simple component that just renders a div with the text “test”.

class AwesomeButton extends React.Component {
  render() {
    return <div>test</div>;
  }
}

Compiled Example - Click Me!

If we look in the render method, we can see what our JSX actually compiles to.

div Compiled Code

Well that’s cool. So our div element just gets defined as the first parameter of React.createElement. I wonder what happens if we try this with our element injecting AwesomeButton?

class AwesomeButton extends React.Component {
  render() {
    const { children, tag = "button", ...other } = this.props;
    const Tag = tag;

    return <Tag {...other}>{children}</Tag>;
  }
}

<AwesomeButton>Click Me!</AwesomeButton>

Looking at the render method in AwesomeButton:

AwesomeButton Compiled Code

Woah, our Tag variable just gets put where the element is! This is exactly the same to how components are treated in JSX. They get ploped straight into the first argument of React.createElement. Now, when the boss asks for a new feature in AwesomeButton, we only have to change this one implementation of the component. All consumers of the component might use it vastly differently, but we are able to keep our code DRY and highly reusable!