John Otander

Add a prop to JSX elements with Babel

When you’re processing a JSX AST you might want to add an additional property.

We do this in MDX so that our custom pragma knows the originating element type when rendering. We do this in Blocks in order to add a unique identifier property so we can later transform, remove, and reorder elements in a visual editor.

In order to add a property, we first need to visit all JSX elements. We can directly visit the JSX opening element since it will contain all prop information.

It’s a more specific visitor than JSXElement. JSXOpeningElement is a child, or descendent, of JSXElement.

export default api => {
  const { types: t } = api

  return {
    visitor: {
      JSXOpeningElement(path) {
        console.log(path)
      }
    }
  }
}

We’ll now be logging out the full path of the node. This means that it has additional information than just the AST node itself. This means we have access to ancestral information, its children, and convenient helper methods for working with the AST.

We won’t need to use any of this for now, but it’s useful if you’re creating more complex transformations.

Now, let’s say we want to add favoriteColor='tomato' as a prop to all JSX elements, we can now add that to our visitor.

First we have to construct the AST representation of the prop:

export default api => {
  const { types: t } = api

  return {
    visitor: {
      JSXOpeningElement(path) {
        const newProp = t.jSXAttribute(
          t.jSXIdentifier('favoriteColor'),
          t.stringLiteral('tomato')
        )
      }
    }
  }
}

Then we need to add it to the element itself:

export default api => {
  const { types: t } = api

  return {
    visitor: {
      JSXOpeningElement(path) {
        const newProp = t.jSXAttribute(
          t.jSXIdentifier('favoriteColor'),
          t.stringLiteral('tomato')
        )

        path.node.attributes.push(newProp)
      }
    }
  }
}

Now the following input:

<Hello />

Will result in:

<Hello favoriteColor="tomato" />

Though, what happens if there’s already a favoriteColor prop?

<Hello favoriteColor="purple" />

Would result in:

<Hello favoriteColor="purple" favoriteColor="tomato" />

Ultimately, with JSX this will still result in the desired outcome because our new prop will override the existing. But, it doesn’t look as nice as what we’d actually write.

So, we can check for the existence of the prop and override it if it exists:

export default api => {
  const { types: t } = api

  return {
    visitor: {
      JSXOpeningElement(path) {
        const existingProp = path.node.attributes.find(
          node => node.name && node.name.name === 'favoriteColor'
        )

        if (existingProp) {
          existingProp.node.value.value === 'tomato'
          return
        }

        const newProp = t.jSXAttribute(
          t.jSXIdentifier('favoriteColor'),
          t.stringLiteral('tomato')
        )

        path.node.attributes.push(newProp)
      }
    }
  }
}

And now the following input:

<Hello favoriteColor="purple" />

Would result in:

<Hello favoriteColor="tomato" />

Sign up for my newsletter

If you want early access to what I'm researching, writing, and building, you should subscribe to my newsletter.

No spam, ever. You can unsubscribe at any time.