Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Server-Rendered Global State

Welcome back! In our last lesson, we explored how to convert a client-heavy React controlled form into a progressively enhanced HTMX form, establishing your Express server as the single source of truth for validation logic. This was our first major step in translating complex client-side patterns into the hypermedia paradigm.

Today, we will address another fundamental aspect of modern web applications: managing global application state. Your learning objective is to replace read-only global state from React Context (e.g., a user profile in a header) by rendering it from the server on each request. We'll see how the HTMX philosophy elegantly simplifies what is often a source of complexity in single-page applications.

The React Way: Global State with Context

As a seasoned React developer, you're undoubtedly familiar with using React.Context to manage global state. A common pattern involves creating a provider, such as UserProvider, at the top of your component tree. This provider fetches user data and makes it available to any child component—like a header displaying a username or a navigation bar showing a "Logout" button—via the useContext hook.

While effective, this approach firmly places the responsibility of fetching, storing, and distributing this state onto the client. The browser becomes a stateful container, and a significant amount of JavaScript is dedicated to managing this state.

The Hypermedia Shift: State Belongs on the Server

HTMX encourages a radical simplification: the client should be as stateless as possible. The canonical source of truth for all state—whether it's the content of a list, the validity of a form, or the identity of the current user—resides on the server.

The client's job is simply to render the HTML that the server provides. This shift eliminates entire categories of client-side complexity, such as state synchronization, caching, and hydration.

This diagram illustrates the core architectural difference. React applications often involve complex client-side state management, while HTMX applications offload this logic to the server, receiving ready-to-render HTML.

This isn't a new idea, but rather a return to the original architecture of the web, enhanced with modern capabilities. The article "htmx and HTML Driven Development" articulates this perfectly.

htmx and HTML Driven Development - DEV Community

This article discusses how HTMX avoids the state synchronization problems inherent in many client-side frameworks by keeping the state on the server.

Please read the section titled No State synchronization problems. The key takeaway here is the concept of the client as a "dummy placeholder for rendering state changes," which is the mental model we'll be using.

So, how do we apply this? Your user's authentication status and profile information are managed on the server, typically within a session. The challenge is to ensure the UI, particularly shared components like a header, always reflects this server-side state.

Solution 1: Server-Side Rendering on Every Request

The most straightforward approach applies to full page navigation. When a user clicks a standard link (<a href="/dashboard">) or navigates to a new URL, your Express server handles a new GET request.

In your Express application, you can use middleware to access the user's session data and make it available to your templates.

// Example Express middleware
app.use((req, res, next) => {
  // Assume user is stored in the session
  res.locals.user = req.session.user;
  next();
});

With res.locals.user available, your main layout template can then render the header (or any other component) conditionally.

<!-- In your main layout.ejs -->
<body>
  <%- include('partials/_header', { user: user }) %>
  <main>
    <!-- Page-specific content goes here -->
  </main>
</body>

The _header.ejs partial would contain the logic:

<!-- In partials/_header.ejs -->
<header id="main-header">
  <div class="logo">MyApp</div>
  <nav>
    <% if (user) { %>
      <span>Welcome, <%= user.name %></span>
      <a href="/logout">Logout</a>
    <% } else { %>
      <a href="/login">Login</a>
    <% } %>
  </nav>
</header>

On every full page load, the server generates the correct header based on the current session state. This is simple, robust, and requires no client-side JavaScript.

Solution 2: Updating Global UI with Out-of-Band Swaps

But what happens when an action within a page changes that global state? For example, submitting a login form via HTMX. The user isn't navigating to a new page, but the header needs to update from "Login" to "Welcome, User."

This is where out-of-band (OOB) swaps come in. You were introduced to this concept in our last lesson on form validation. Now, we'll use it to update a completely separate part of the page.

An OOB swap allows a single server response to update multiple, disjoint elements in the DOM. The server sends back the primary HTML fragment for the main target, plus additional fragments marked with hx-swap-oob="true". HTMX finds the elements on the page with matching IDs and swaps them.

Jack Herrington's "HTMX For React Developers" video has a wonderfully clear example of this mechanism.

HTMX For React Developers in 10 Minutes

This video provides a concise demonstration of out-of-band swaps to update two different counters on a page with a single button click.

Watch the segment from on updating multiple places. Pay close attention to how the server response contains one main fragment and a second div with an id and the hx-swap-oob="true" attribute. This is the exact pattern we will use for our header.

Let's apply this to our login flow. Imagine a login form submitted with hx-post="/login". The Express route handler would:

  1. Validate credentials.
  2. Update the user's session.
  3. Render a response containing both the main content update (e.g., a welcome message) and the updated header as an OOB fragment.

The server's response would look like this:

<!-- The primary response for the login form's target -->
<div id="login-form-container">
  <p>Login successful! Welcome back.</p>
</div>

<!-- The OOB fragment to update the header -->
<header id="main-header" hx-swap-oob="true">
  <div class="logo">MyApp</div>
  <nav>
    <span>Welcome, Alex</span>
    <a href="/logout" hx-post="/logout">Logout</a>
  </nav>
</header>

HTMX receives this response, swaps the div#login-form-container as usual, then sees the OOB fragment. It finds the <header id="main-header"> element already in the DOM and swaps its content with the new header HTML. The user sees both the login form disappear and the main header update, all from a single request.

A Deeper Dive: Polling and OOB for Live Data

We can extend this pattern to handle periodically updated read-only data, like a notification count in the header. Instead of a user action, the trigger can be a timer. This is demonstrated masterfully in the hypermedia-tv video on OOB swaps.

The power of htmx's hx-swap-oob

This video provides a more advanced use case, building a live dashboard where multiple widgets update periodically using a polling mechanism combined with OOB swaps.

First, watch the initial setup from where the polling is configured. Note the key attributes on the <body> tag: hx-get, hx-trigger="every 5s", and hx-swap="none". The hx-swap="none" is crucial: it tells HTMX to discard the main response body by default. Next, watch from to understand the core mechanic. The video explains that by adding id and hx-swap-oob="true" to specific elements, you are telling HTMX to "rescue" those elements from the full-page response fetched in the background and swap them into the live DOM.

By combining polling with OOB swaps, you can update any number of "global" UI elements without complex client-side state management, all while keeping the server as the single source of truth.

Conclusion

Today, you've learned a powerful and simple pattern for handling what is often a complex task in SPAs: managing global state. By embracing the hypermedia principle of a server-rendered UI, you can eliminate the need for client-side state containers like React Context for read-only data.

Key Takeaways:

  • Server Holds the State: In the HTMX model, global application state (like user identity) lives on the server, typically in a session.
  • Render on Request: The UI reflects this state because the server renders the necessary HTML with the latest data on every request.
  • OOB Swaps for Dynamic Updates: For in-page actions that change global state (like login/logout), use out-of-band (hx-swap-oob) swaps to update shared UI elements like headers from a single server response.
  • Polling for Live Data: This same OOB pattern can be combined with a polling trigger to create "live" data displays (e.g., notification counts) with minimal code.

You've now seen how to replace React.Context for read-only data. In our next lesson, we will build directly on this foundation. We'll tackle how to translate a cross-component state update from Redux/Context into an HTMX action that uses out-of-band swaps to refresh a separate region, such as updating a shopping cart counter when adding an item. You'll find that the OOB swap mechanism you learned today is the key to that problem as well.

Can't find a good explanation? Sign up and we'll make it for you

Sign up