|
| 1 | +# Table Rendering Limitations |
| 2 | + |
| 3 | +When using components inside HTML `<table>` elements, you must follow strict HTML structure rules. Failing to do so can cause reactivity issues that may look like state updates are not working. |
| 4 | + |
| 5 | +This is not a bug in **Django Unicorn**, but a consequence of how browsers handle table DOM structure. |
| 6 | + |
| 7 | +## Why This Happens |
| 8 | + |
| 9 | +HTML tables are **structurally strict**. Browsers automatically correct invalid markup inside table elements. |
| 10 | + |
| 11 | +Valid structure rules: |
| 12 | +* `<table>` may contain `<thead>`, `<tbody>`, `<tfoot>` |
| 13 | +* `<tbody>` may contain **only** `<tr>` |
| 14 | +* `<tr>` may contain **only** `<td>` or `<th>` |
| 15 | +* `<td>` may contain flow content (`<div>`, `<span>`, etc.) |
| 16 | + |
| 17 | +If a component rendered inside a `<tbody>` outputs something like: |
| 18 | + |
| 19 | +```html |
| 20 | +<tr> |
| 21 | + <td>Row content</td> |
| 22 | +</tr> |
| 23 | + |
| 24 | +<div class="modal">...</div> |
| 25 | +``` |
| 26 | + |
| 27 | +The `<div>` is **invalid inside `<tbody>`**, so the browser will automatically move it elsewhere in the DOM. |
| 28 | + |
| 29 | +When this happens: |
| 30 | +1. The browser modifies the DOM structure. |
| 31 | +2. Unicorn's DOM diffing no longer matches the expected structure. |
| 32 | +3. Reactive updates may silently fail or appear inconsistent. |
| 33 | + |
| 34 | +This commonly appears as: |
| 35 | +* A modal not showing reactively |
| 36 | +* Conditional blocks not updating |
| 37 | +* Child components not re-rendering properly |
| 38 | + |
| 39 | +## Example of Problematic Pattern |
| 40 | + |
| 41 | +Parent template: |
| 42 | + |
| 43 | +```html |
| 44 | +<tbody> |
| 45 | + {% for item in items %} |
| 46 | + {% unicorn 'row-component' item=item key=item.id %} |
| 47 | + {% endfor %} |
| 48 | +</tbody> |
| 49 | +``` |
| 50 | + |
| 51 | +Child component template: |
| 52 | + |
| 53 | +```html |
| 54 | +<tr> |
| 55 | + <td>{{ item.name }}</td> |
| 56 | + <td> |
| 57 | + <button unicorn:click="show_modal">Delete</button> |
| 58 | + </td> |
| 59 | +</tr> |
| 60 | + |
| 61 | +{% if modal_visible %} |
| 62 | + <div class="modal">Are you sure?</div> |
| 63 | +{% endif %} |
| 64 | +``` |
| 65 | + |
| 66 | +The `<div>` rendered after `<tr>` is invalid inside `<tbody>` and will be relocated by the browser. |
| 67 | + |
| 68 | +## Why It Works With `<div>` |
| 69 | + |
| 70 | +Replacing table tags with `<div>` works because `<div>` elements have no structural constraints. The browser does not auto-correct their placement, so Unicorn's DOM diffing remains stable. |
| 71 | + |
| 72 | +## Recommended Solutions |
| 73 | + |
| 74 | +### 1. Move Modals Outside the Table (Recommended) |
| 75 | + |
| 76 | +Control modal state from the parent component and render the modal outside the `<table>`. |
| 77 | + |
| 78 | +Child component: |
| 79 | + |
| 80 | +```python |
| 81 | +def request_delete(self): |
| 82 | + self.parent.confirm_delete(self.item.id) |
| 83 | +``` |
| 84 | + |
| 85 | +Parent component: |
| 86 | + |
| 87 | +```python |
| 88 | +selected_id = None |
| 89 | +show_modal = False |
| 90 | + |
| 91 | +def confirm_delete(self, item_id): |
| 92 | + self.selected_id = item_id |
| 93 | + self.show_modal = True |
| 94 | +``` |
| 95 | + |
| 96 | +Render the modal below the table: |
| 97 | + |
| 98 | +```html |
| 99 | +</table> |
| 100 | + |
| 101 | +{% if show_modal %} |
| 102 | + <div class="modal">...</div> |
| 103 | +{% endif %} |
| 104 | +``` |
| 105 | + |
| 106 | +This keeps table markup valid and avoids DOM restructuring. |
| 107 | + |
| 108 | +### 2. Render Modal Inside a Valid `<td>` |
| 109 | + |
| 110 | +If you must render it within the table, wrap it inside a `<td colspan="...">`: |
| 111 | + |
| 112 | +```html |
| 113 | +{% if modal_visible %} |
| 114 | +<tr> |
| 115 | + <td colspan="5"> |
| 116 | + <div class="modal">...</div> |
| 117 | + </td> |
| 118 | +</tr> |
| 119 | +{% endif %} |
| 120 | +``` |
| 121 | + |
| 122 | +This preserves valid table structure. |
| 123 | + |
| 124 | +### 3. Ensure Single Valid Root Per Component |
| 125 | + |
| 126 | +When rendering a component inside `<tbody>`, its root element must be a `<tr>`. |
| 127 | +When rendering inside `<tr>`, its root must be `<td>` or `<th>`. |
| 128 | + |
| 129 | +Avoid rendering sibling elements that break table hierarchy. |
| 130 | + |
| 131 | +## Key Takeaway |
| 132 | + |
| 133 | +If you experience reactivity issues inside tables: |
| 134 | +* Verify your component outputs valid HTML table structure. |
| 135 | +* Ensure no `<div>` or non-table elements are placed directly inside `<tbody>` or `<tr>`. |
| 136 | +* Prefer handling overlays and modals outside the table. |
| 137 | + |
| 138 | +Table elements are one of the strictest parts of HTML. Following valid structure rules ensures Unicorn's DOM diffing remains reliable and reactive updates function correctly. |
0 commit comments