Create your own
Lesson illustration

Classifying React CRUD Components with the Migration Rubric

In the previous lesson, we constructed a decision rubric to guide your migration from React to HTMX. This framework helps you classify any React component into one of three targets: a full server-rendered page, an HTMX-driven partial, or a client-only component. Now, it's time to move from theory to practice.

This lesson will focus on applying that rubric to a real-world, complex feature you're very familiar with: a data table with sorting, filtering, and pagination. By deconstructing a typical React implementation of this feature, you will learn how to map its pieces to the hypermedia architecture. Our goal is to translate a single, stateful React component tree into a cohesive set of server endpoints and HTML fragments orchestrated by HTMX. This process is the core of your migration playbook.

This image illustrates the common server-side patterns that power the dynamic HTML fragments we will be designing, forming the backbone of an HTMX-driven data table.

Case Study: Deconstructing a React Data Table

Let's consider a feature common in the CRUD applications you've built: a data table for managing a list of projects. This is not a simple list; it's a rich, interactive component.

A typical React data table, featuring sorting, filtering, and pagination. This is the kind of complex, stateful component we will analyze for migration.

As a senior developer, you would likely structure this in React using a clear component hierarchy. Let's outline a plausible structure:

  • ProjectsPage: The top-level route component from React Router.
  • DataTableContainer: The "smart" component. It holds the state for filters, sorting, and the current page. It's responsible for fetching data, likely using a library like React Query or SWR to manage API requests, caching, and loading/error states.
  • FilterControls: A set of inputs (e.g., text search, status dropdown) that update the state in DataTableContainer.
  • DataTable: The main table structure (<table>). It receives the data array and column definitions as props.
    • TableHeader: Renders <thead> with <th> cells. These are often clickable to trigger sorting.
    • DataRow: Renders a <tr> for a single project. It might include "dumb" presentational sub-components like a StatusBadge. It also contains action buttons (e.g., Edit, Delete).
  • PaginationControls: "Previous" and "Next" buttons, and maybe a page number display/input, to control which slice of data is shown.

Our task is to take this component tree and apply the rubric from our last lesson to each piece.

Applying the Migration Rubric

We'll work through the component tree, applying our two key questions:

  1. Does this component's functionality depend on server state?
  2. What is the required interactivity level and complexity?

1. ProjectsPage and DataTableContainer

  • React Role: Manages the overall page route and orchestrates all data fetching and state management for the table (filters, sorting, pagination). This is the stateful heart of the feature.
  • Rubric Analysis:
    • Server State? Yes. Its entire purpose is to fetch and manage a representation of the projects collection from your database. The filter, sort, and page parameters are just inputs to a server-side query.
    • Interactivity? Medium. It re-fetches data based on user actions.
  • Migration Target: Server-Rendered Page and HTMX Partial.
    • The ProjectsPage component maps to a primary Express route (e.g., app.get('/projects', ...)) that renders a full HTML document (projects.ejs). This document will contain the initial state of the entire data table.
    • The data-fetching and re-rendering logic previously in DataTableContainer moves to a server endpoint that returns just the table's HTML. Critically, this can be the same Express route, which checks for the HX-Request header to decide whether to return the full page or just the partial.

2. FilterControls (Search, Dropdowns)

  • React Role: Controlled components whose state (searchTerm, filterStatus) is lifted up to DataTableContainer. A change triggers a re-fetch.
  • Rubric Analysis:
    • Server State? Yes. The state of these controls directly translates into query parameters for the server.
    • Interactivity? Medium. Changes trigger a server request.
  • Migration Target: Part of an HTMX Partial's trigger mechanism.
    • These controls will become standard HTML <form> elements. The <input> for search and <select> for filtering will have name attributes corresponding to the server's expected query parameters.
    • The form itself will use hx-get to point to our /projects endpoint, hx-target to specify that only the table should be replaced, and hx-trigger to automatically submit the form on input changes (with a debounce).

The video "You don't need a frontend framework" by Andrew Schmelyun provides an excellent walkthrough of this exact pattern. It shows how a simple form can be progressively enhanced to filter a table without a full page reload, first with vanilla JS and then simplified further with HTMX.

You don't need a frontend framework

This video demonstrates the core pattern of converting React's controlled inputs into a server-driven form with HTMX.

First, watch the segment from this point where the presenter refactors a simple HTML form to use vanilla JavaScript with fetch() to reload only the table content. This is conceptually what React is doing under the hood. Then, watch the following section from this transition, where he replaces that vanilla JS with a few HTMX attributes on the form to achieve the same result with far less code. This is the exact pattern we'll apply to our FilterControls.

3. DataTable, TableHeader, DataRow

  • React Role: Renders the data. TableHeader handles sort clicks, and DataRow contains action buttons (e.g., delete).
  • Rubric Analysis:
    • Server State? Yes. The content is a direct render of server data. Actions like sorting and deleting are mutations of server state.
    • Interactivity? Medium. Clicks trigger server requests.
  • Migration Target: HTMX Partial.
    • The <table> element (or just its <tbody>) becomes the target for swaps (e.g., <table id="projects-table">...</table>).
    • Sorting: Each <th> will contain an <a> tag or <button> with an hx-get attribute that includes the sort parameters (e.g., hx-get="/projects?sort=name&dir=asc"). It will target #projects-table.
    • Deleting: The delete button within a DataRow will use hx-delete pointing to an endpoint like /projects/123. Crucially, it will use hx-target="closest tr" and hx-swap="outerHTML" to remove the entire table row from the DOM on a successful response.

The article "Advanced Data Table with HTMX" provides a superb, detailed implementation of these very patterns.

Advanced Data Table with HTMX - Benoit Averty

This article provides concrete HTML examples for implementing the features we are discussing. It's a practical blueprint for building the HTMX version of our data table.

Please read the following sections to see the code-level implementation: Focus on the section Deleting an Element. This shows the exact hx-delete, hx-target, and hx-swap pattern for removing a row. Next, read through Pagination / Sorting / Filtering. This explains how to use a single form to manage the state of all controls and how clickable links in headers can be used for sorting while maintaining the filter/page state.

4. PaginationControls

  • React Role: Renders "Next/Previous" buttons. Clicks update the page state in DataTableContainer, triggering a re-fetch.
  • Rubric Analysis:
    • Server State? Yes. The page number is a server query parameter.
    • Interactivity? Medium. Clicks trigger a server request.
  • Migration Target: Part of the HTMX Partial ecosystem.
    • These will be simple <a> tags or <button> elements with hx-get attributes pointing to the next/previous page URL (e.g., hx-get="/projects?page=3&..."). They will target the #projects-table. The server is responsible for rendering these links with the correct URLs based on the current state.

5. StatusBadge

  • React Role: A "dumb" presentational component that just displays a value.
  • Rubric Analysis:
    • Server State? No. It has no state of its own. It's just a visual representation of a prop.
    • Interactivity? None.
  • Migration Target: Absorbed into Parent HTML.
    • This component doesn't migrate; it dissolves. The logic to render a green pill for "On Track" or a red one for "At Risk" will exist directly within the EJS template for the table row (_row.ejs). It's just conditional HTML.

Synthesizing the New Hypermedia Architecture

By applying the rubric, we've transformed the React component tree into a server-centric architecture:

React ComponentMigration TargetImplementation Notes
ProjectsPageServer-Rendered PageAn Express route GET /projects serves a full EJS template.
DataTableContainerLogic moves to ServerThe state management and data fetching logic now lives inside the GET /projects route handler.
FilterControlsHTML Form with HTMXA <form> wraps the search/filter inputs. hx-get, hx-target, and hx-trigger handle updates.
DataTableHTMX Partial TargetA <div id="projects-table"> or similar element that gets replaced by HTMX swaps.
TableHeader (Sort)HTMX-enhanced Links<a> tags with hx-get and hx-target to re-fetch and swap the table with new sorting.
DataRow (Delete)HTMX-enhanced ButtonA <button> with hx-delete, hx-target="closest tr", and hx-swap="outerHTML".
PaginationControlsHTMX-enhanced Links<a> tags with hx-get to fetch a different page and swap the table content.
StatusBadgeDissolved into TemplateBecomes conditional logic within the server-side row partial (_row.ejs).

Instead of a single, complex client-side state object, the application state now primarily lives in two places:

  1. The Server: The database holds the true state. The Express route handler is the single source of truth for querying and rendering that state.
  2. The URL: The filter, sort, and pagination state is encoded in the URL's query parameters. HTMX's hx-push-url="true" attribute makes this seamless, preserving shareability and browser history.

This approach aligns perfectly with the hypermedia principles discussed in the "htmx in 2026" article, where it notes that for CRUD interfaces, the htmx version is dramatically simpler than the equivalent React implementation involving state management, data fetching hooks, and mutation logic.

Conclusion

In this lesson, we put our migration rubric to the test. We successfully deconstructed a complex, stateful React data table and mapped its constituent parts to a new, simpler architecture based on server-rendered HTML fragments.

Key Takeaways:

  • A single, "smart" React container component often maps to a server-side route handler that serves both full pages and partial fragments.
  • Interactive elements that manipulate server state (filters, sorters, paginators, delete buttons) become simple HTML elements enhanced with hx-* attributes.
  • The complex client-side state management of React is largely replaced by the server's business logic and the state encoded in the URL.
  • "Dumb" presentational components often don't have a direct equivalent; they are simply absorbed into the structure of the server-side templates.

You have now moved from understanding the "what" and "why" of the migration rubric to the "how." You have a clear mental model for dissecting your existing applications.

In the next lesson, we will formalize this process. You will learn how to draft a component-by-component migration plan for a small React application, creating a concrete document that maps each component to its target implementation and the specific server endpoints it will require.

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

Sign up