Skip to main content

Introduction

Auto Refresh offers a way to re-render views of your application when the underlying data changes. This is useful when you want your views to always reflect the live database state. Auto Refresh can be an easy replacement for manually polling for changes using AJAX.

Use Cases

1

Deployment Status

Used in Shipnix to display the current deployment status. Whenever the deployment progress or status changes, the view gets updated automatically.
2

Job Queue Monitoring

Building a monitoring tool for background job workers. Using auto refresh, the view can always represent the current state of all the job queues.
3

Social Media Feed

Building a small social media site: Automatically display new posts in the feed when they become available.

Setup

Auto Refresh requires no global configuration. Just make sure these two components are in your layout:

1. Add Meta Tag to Layout

In your Web/View/Layout.hs, add {autoRefreshMeta} inside the <head> section:
metaTags :: Html
metaTags = [hsx|
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    {autoRefreshMeta}
|]

2. Include Required JavaScript

In your Web/View/Layout.hs, ensure these scripts are included (order matters - morphdom must come before ihp-auto-refresh):
scripts :: Html
scripts = [hsx|
    <script src={assetPath "/vendor/morphdom-umd.min.js"}></script>
    <script src={assetPath "/ihp-auto-refresh.js"}></script>
    <!-- ... other scripts ... -->
|]
Once these two components are in place, you can use autoRefresh in your actions — no middleware setup needed. The auto-refresh server is created lazily on first use.

How It Works

It’s good to have a general understanding of how IHP Auto Refresh works.
1

Activation

Auto Refresh is activated for an action by calling autoRefresh. The framework automatically tracks all tables your action is using (e.g., in SELECT * FROM ... queries).
2

Watching for Changes

Once the action sends a response, IHP starts watching for any INSERT, UPDATE, or DELETE statement to all the tables used by your action.
3

WebSocket Connection

When the page is rendered, a small JavaScript function connects back to the IHP server using a WebSocket connection.
4

Re-rendering

Whenever an INSERT, UPDATE, or DELETE happens to the watched tables, IHP reruns your action on the server-side. When the generated HTML looks different than the HTML generated on the initial page load, it sends the new HTML to the browser using the WebSocket connection.
5

DOM Updates

The JavaScript listening on the WebSocket uses the new HTML to update the current page. It uses morphdom to only touch the parts of your current DOM that have changed.

Using Auto Refresh

Let’s say we have a ShowProjectAction like this:
action ShowProjectAction { projectId } = do
    project <- fetch projectId
    render ShowView { .. }
To enable auto refresh, add autoRefresh in front of the do:
action ShowProjectAction { projectId } = autoRefresh do
    project <- fetch projectId
    render ShowView { .. }
That’s it! When you open your browser dev tools, you will see that a WebSocket connection has been started when opening the page. When you update the project from a different browser tab, the page instantly updates to reflect your changes.

Advanced Auto Refresh

Auto Refresh Only for Specific Tables

By default IHP tracks all the tables in an action with Auto Refresh enabled. In scenarios where you’re processing a lot of data for a view, but only a small portion needs Auto Refresh, you can enable Auto Refresh only for the specific tables:
action MyAction = do -- We don't enable auto refresh at the action start

    -- This part is not tracked by auto refresh, as `autoRefresh` wasn't called yet
    -- Therefore we can do our "expensive" operations here
    expensiveModels <- query @Expensive |> fetch

    autoRefresh do
        -- Inside this block auto refresh is active and all queries here are tracked
        cheap <- query @Cheap |> fetch
        render MyView { expensiveModels, cheap }

Custom SQL Queries with Auto Refresh

Auto Refresh automatically tracks all tables your action is using by hooking itself into the Query Builder and fetch functions. Let’s say we’re using a custom SQL query:
action StatsAction = autoRefresh do
    dailyNewCompanies <- sqlQuery "SELECT date, COUNT(distinct id) AS count FROM (SELECT date_trunc('day', companies.created_at) AS date, id FROM companies) AS companies_with_date GROUP BY date" ()

    pure StatsView { ..}
When using this custom query with sqlQuery, Auto Refresh is not aware that we’re reading from the companies table. In this case we need to help out Auto Refresh by calling trackTableRead:
action StatsAction = autoRefresh do
    dailyNewCompanies <- sqlQuery "SELECT date, COUNT(distinct id) AS count FROM (SELECT date_trunc('day', companies.created_at) AS date, id FROM companies) AS companies_with_date GROUP BY date" ()

    trackTableRead "companies"

    pure StatsView { ..}
The trackTableRead marks the table as accessed for Auto Refresh and leads to the table being watched.

API Reference

Build docs developers (and LLMs) love