Using HTMX To Solve Server Side Issues
Published: 2023-04-27 16:28:57

Recently I came across an issue where users were getting error 500s on a page that was a list of processed information displayed in a table. 

After close inspection, the problem was a `memory exhausted` error in PHP. 

The users who received this error were users who had a lot more than expected data. 

There were a few ways to fix this, the first was to increase PHP memory limit, the server could have handled the extra memory load, so this was an option.

Another option was to paginate the data. While this was an option worth considering, the type of information, and how it was used, meant that if the data was paginated, the page would lose some of its usefulness. 

The last option considered was to lazy load the data in smaller chunks. This was the option I ended up doing. 

From a server point of view, keeping PHP memory limit at the lower level was good for the server. 

From a user point of view, lazy loading is a good experience, and no more error 500s. 

There are many ways to achieve lazy loading, but I decided that [HTMX](https://htmx.org) was the best option. 

[HTMX](https://htmx.org) is easy to get going and incredibly simple to achieve great results. 

## Enabling HTMX

To enable HTMX, you just need to include the Javascript library on your page. I chose to use the CDN version, but you could download the script and place it on your web server if you prefer. 

```Javascript
<script src="https://unpkg.com/htmx.org@1.9.1" integrity="sha384-KReoNuwj58fe4zgWyjj5a1HrvXYPBeV0a3bNPVjK7n5FdsGC41fHRx6sq5tONeP0" crossorigin="anonymous"></script>
```

My data looked like a typical HTML Table

```HTML
<table>
    <tr>
        <th>Heading</th>
        <th>Heading2</th>
        <th>Heading3</th>
    </tr>
    <tr>
        <td>Data1</td>
        <td>Data2</td>
        <td>Data3</td>
    </tr>
    <tr>
        <td>Data4</td>
        <td>Data5</td>
        <td>Data6</td>
    </tr>
</table>
```

## Backend For Lazy Loading

The only requirement on the backend for lazy loaded data with HTMX is to ensure the AJAX page will return the HTML needed to fill up the page. 

In this case, the backend would return 

```HTML
<tr>
    <td>Data1</td>
    <td>Data2</td>
    <td>Data3</td>
</tr>
<tr>
    <td>Data4</td>
    <td>Data5</td>
    <td>Data6</td>
</tr>
```

## Front End Needs

Often times, you will see examples using Divs, and replacing the data inside the div. 

In this case, a Table was used, so adding a Div to replace the data wasn't really a good option. 

Instead, a table row was added to the page that would be replaced by the initial data from the AJAX call when it first loaded.

```HTML
<table>
    <tr>
        <th>Heading</th>
        <th>Heading2</th>
        <th>Heading3</th>
    </tr>
    <tr id='datalist' 
        hx-get="/path/to/ajax/page" 
        hx-target='#datalist' 
        hx-trigger="load" 
        hx-swap='outerHTML'>
        <td colspan='3'>
            <div class="d-flex justify-content-center">
                <div class="spinner-border" role="status">
                    <span class="sr-only">Loading...</span>
                </div>
            </div>
        </td>
    </tr>
</table>

```

### What does all this do? 

**hx-get**: uses a GET method to retrieve the page specified.
**hx-target**: the ID of the element HTMX will be using to put data into.
**hx-trigger**: How do you want the GET method to be triggered. In this case I wanted it to run the first AJAX call as soon as the page loads. So `load` was used. There are other options available like `click` if you want it to run the AJAX on a click of a button for example.
**hx-swap**: By default HTMX will replace whats inside the target element (innerHTML). Setting this to `outerHTML` will replace the Table Row and replace it with as many table rows as I pull back from the AJAX call.

The Divs provide the user with a spinner that will display until the data is replaced. Giving the user feedback while the data is being retrieved is important. If there is slowness on the network, at least they know it's still trying. 

## Setting up the backend for lazy loading additional chunks of data.

Once the first set of data has been received the datalist row will be replaced and will no longer run the load. 

The data retrieved needs to include (unless all the data has been received) a trigger to load the next chunk of data.

In this case, I chose to trigger the data on `reveal`. When the user scrolls to the bottom of the page, this will reveal the last table row which will have the hx-trigger to load the next chunk

Along with the data needed to display, I added an additional row with the spinner in it again so that when it triggers, the spinner will display and the AJAX call triggered. 

```HTML
<tr>
    <td>Data1</td>
    <td>Data2</td>
    <td>Data3</td>
</tr>
<tr>
    <td>Data4</td>
    <td>Data5</td>
    <td>Data6</td>
</tr>
<tr
    hx-get="/page/to/ajax/page/pagenumber"
    hx-trigger="revealed"
    hx-target='this' 
    hx-swap="outerHTML">
    <td colspan=3>
        <div class="d-flex justify-content-center">
            <div class="spinner-border" role="status">
                <span class="sr-only">Loading...</span>
            </div>
       </div>
    </td>
</tr>
```

By adding this last table row in, the data will keep loading in new data, but not replacing the current data, it will only replace the trigger row. In this case, this was the ultimate goal, to end up displaying all the data, but loading it in such a way, that PHP didn't run out of memory. 

This was easy to implement, provided a much better end user experience and solved the PHP memory exhaustion issues.

HTMX can do a lot more than just this, but it was a practical use case for it and show cases its potential.