Don’t let data for Alpine.js components blow up your web site

On one of my early projects with Alpine.js I needed to pass some structured data about videos to show in a carousel. The data was an array of objects with key/value pairs like:

  • Url
  • Title
  • Description
  • Thumbnail image

I was working with PHP on WordPress and the easy solution was to simply pass the data as an argument to the component like this:

<div
    x-data='videoCarousel(<?php echo json_encode($video_data); ?>)'
>
…
</div>

This implementation relies on the fact that JSON data is encoded with double quotes around strings so I should be safe using single quotes for the attribute. It worked fine for a while, then one day the page just broke. I of course got a call from the client and looked into it. You may already know what the issue was… they used a single quote in the description which broke the markup.

As a quick, short-term fix, I went in and edited the text to use a curly quote; then I figured out a long term fix. This long term fix became a pattern that I use all the time now: embedding data in a script template and parsing it into my Alpine.js component during init.

Embedding component data with a script tag lets you safely support varying and even unexpected user content safely.

Embedding the data for Alpine.js

The simplest version of the embedding code looks like this:

<script type="text/data" x-ref="data"><?php echo json_encode($video_data); ?></script>

There are a few key parts to this:

  1. We are still outputting JSON-encoded data that we will read in the JS
  2. The type on the script tag is text/data – with this type the browser will not try to parse and execute the code as JavaScript. The data will just remain present in the page for later use
  3. Assigning an x-ref value lets us easily get the data inside the Alpine.js component

Parsing the data in the Alpine component

Here is a subset of the component with the important parts to read the data.

export default () => ({
    data: [],
    init() {
        try {
            this.options = JSON.parse(
                this.$refs.data.textContent 
            );
        } catch(error) {
            // handle exception
            console.error("Can't parse data:", error);
        }
    }
})

How this works:

  1. The init function is run automatically when the component is loaded
  2. We use the reference that Alpine.js sets up for us to access the JSON data in the script tag
  3. The JSON data is parsed and stored in the data property
  4. Everything is wrapped in a try /catch block for safety – though it’s highly unlikely to be an issue since we control both the embedding and the parsing code

Takeaways

I’ve used this pattern on a number of different projects since then. Some examples include:

  • options for a filter control
  • content for a quote carousel
  • data for a paginated, filterable card list

It’s been a great solution and provides a solid way to pass more complicated data into an Alpine.js component safely.

Want to learn more about using Alpine.js? Check out my new video course: Exploring Alpine.js