Implement JQHTML function cache ID system and fix bundle compilation Implement underscore prefix for system tables Fix JS syntax linter to support decorators and grant exception to Task system SPA: Update planning docs and wishlists with remaining features SPA: Document Navigation API abandonment and future enhancements Implement SPA browser integration with History API (Phase 1) Convert contacts view page to SPA action Convert clients pages to SPA actions and document conversion procedure SPA: Merge GET parameters and update documentation Implement SPA route URL generation in JavaScript and PHP Implement SPA bootstrap controller architecture Add SPA routing manual page (rsx:man spa) Add SPA routing documentation to CLAUDE.md Phase 4 Complete: Client-side SPA routing implementation Update get_routes() consumers for unified route structure Complete SPA Phase 3: PHP-side route type detection and is_spa flag Restore unified routes structure and Manifest_Query class Refactor route indexing and add SPA infrastructure Phase 3 Complete: SPA route registration in manifest Implement SPA Phase 2: Extract router code and test decorators Rename Jqhtml_Component to Component and complete SPA foundation setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
Executable File
Research Report: jqhtml Component Loading State Pattern Issue
Date: 2025-10-10 Author: AI Assistant (Claude) Subject: Critical documentation gap in jqhtml component lifecycle - loading state anti-patterns
Executive Summary
During the development of a DataGrid component for a real-world RSpade application, a critical pattern mistake was identified in how jqhtml components handle loading states. The issue stems from incomplete documentation of jqhtml's automatic re-rendering behavior and lifecycle constraints, leading developers to implement incorrect loading patterns that violate the framework's design principles.
Impact: Medium-High - This anti-pattern can be implemented without immediate errors but leads to unpredictable component behavior, violates the "fail loud" philosophy, and creates maintenance issues.
Recommendation: Update jqhtml lifecycle documentation to explicitly document the correct loading state pattern and common anti-patterns with clear examples.
Problem Statement
What Happened
An AI assistant (myself) was tasked with implementing a DataGrid component that loads data asynchronously from a server endpoint. Following common patterns from other frameworks (React, Vue, Angular), the implementation used:
- Manual
this.render()calls inon_load()to trigger re-rendering - Complex nested state objects (
this.data.state.loading) to track loading status - Loading flags set at the START of
on_load()before data fetching
After framework update, the jqhtml runtime emitted a warning about setting properties other than this.data in on_load(). The implementation was corrected to use this.data.state instead of this.state, but this still violated the intended pattern.
The user (framework creator) observed the implementation and provided corrective guidance, demonstrating the correct pattern:
- NO manual
this.render()calls - Framework handles re-rendering automatically - Simple flat
this.data.loadedflag - Set totrueat END ofon_load() - Template-level loading checks - Use
<% if (!this.data.loaded) %>in jqhtml template - Trust automatic re-rendering - Framework detects
this.datachanges and re-renders
Incorrect Implementation (What I Did)
JavaScript:
class Contacts_DataGrid extends Component {
async on_load() {
// ❌ WRONG: Setting loading state at START
this.data.state = {loading: true};
this.render(); // ❌ WRONG: Manual render call
try {
const response = await $.ajax({...}); // ❌ WRONG: $.ajax() instead of controller stub
if (response.success) {
this.data = {
records: response.records,
total: response.total,
state: {loading: false} // ❌ WRONG: Complex nested state
};
}
} catch (error) {
this.data.state = {loading: false, error: true};
}
}
}
Template:
<% if (this.data.state && this.data.state.loading) { %>
<!-- ❌ WRONG: Complex state check -->
<div>Loading...</div>
<% } else if (this.data.records && this.data.records.length > 0) { %>
<!-- Data display -->
<% } %>
Correct Implementation (User's Correction)
JavaScript:
class Contacts_DataGrid extends Component {
async on_load() {
// ✅ CORRECT: NO loading flags at start
// ✅ CORRECT: NO manual this.render() calls
// ✅ CORRECT: Use Ajax endpoint pattern
const response = await Frontend_Contacts_Controller.datagrid_fetch({
page: 1,
per_page: 25,
sort: 'id',
order: 'asc'
});
// ✅ CORRECT: Populate this.data directly
this.data.records = response.records;
this.data.total = response.total;
this.data.page = response.page;
this.data.per_page = response.per_page;
this.data.total_pages = response.total_pages;
// ✅ CORRECT: Simple flag at END
this.data.loaded = true;
// ✅ Automatic re-render happens because this.data changed
}
}
Template:
<% if (!this.data || !this.data.loaded) { %>
<!-- ✅ FIRST RENDER: Loading state (this.data is empty {}) -->
<div class="loading-spinner text-center py-5">
<div class="spinner-border text-primary mb-3"></div>
<p class="text-muted">Loading contacts...</p>
</div>
<% } else if (this.data.records && this.data.records.length > 0) { %>
<!-- ✅ SECOND RENDER: Data loaded -->
<table class="table">
<% for (let record of this.data.records) { %>
<tr><!-- row content --></tr>
<% } %>
</table>
<% } else { %>
<!-- ✅ Empty state -->
<div class="text-center py-5">
<p class="text-muted">No records found</p>
</div>
<% } %>
Root Cause Analysis
Why This Mistake Happened
-
Documentation Gap: Existing jqhtml documentation focused on:
- What
on_load()should do (load async data) - What
on_load()CANNOT do (DOM manipulation) - Restriction to only modify
this.data
But it did not explicitly document:
- The automatic re-rendering behavior when
this.datachanges - The correct loading state pattern (
this.data.loadedflag at END) - Common anti-patterns (manual
this.render(), nested state objects)
- What
-
Framework Paradigm Difference: jqhtml's approach differs from mainstream frameworks:
- React/Vue: Explicit state management, manual render triggers
- jqhtml: Automatic re-rendering on
this.datachange, trust the framework
Developers with React/Vue experience naturally implement familiar patterns that violate jqhtml's design.
-
Silent Failure Mode: The incorrect implementation appeared to work:
- No runtime errors
- Visual output seemed correct
- Only violated best practices, not hard constraints
This violates RSpade's "fail loud" philosophy - the framework should have caught this earlier.
Why It Matters
-
Unpredictable Behavior: Manual
this.render()calls can interfere with the framework's automatic re-rendering, causing double-renders or race conditions. -
Maintenance Burden: Complex nested state objects (
this.data.state.loading) add cognitive overhead and make code harder to understand. -
Framework Violation: Setting loading state at the START of
on_load()means the first render triggers before data loading begins, defeating the purpose of the double-render pattern. -
Philosophy Violation: The pattern violates RSpade's "trust the framework" principle - manually calling
this.render()indicates lack of trust in automatic re-rendering.
How the Pattern SHOULD Work
The Double-Render Pattern
jqhtml components that load data in on_load() automatically render twice:
Render 1 (Initial):
- Template executes
this.data = {}(empty object)- DOM created with loading state (
<% if (!this.data.loaded) %>is true) - User sees loading spinner
on_load() Executes:
- Fetches data from server
- Populates
this.data.records,this.data.total, etc. - Sets
this.data.loaded = trueat END - Framework detects
this.datachanged
Render 2 (Automatic):
- Framework automatically re-renders template
this.data.loaded === truenow- Template shows data (
<% } else if (this.data.records.length > 0) %>) - User sees actual data
on_ready() Fires:
- Called ONCE after second render
- All children components ready
- Safe to attach event handlers and DOM manipulation
Key Principles
-
Trust Automatic Re-Rendering
- Framework watches
this.datafor changes - Modifying
this.datatriggers automatic re-render - NEVER manually call
this.render()inon_load()
- Framework watches
-
Simple Flat State
- Use
this.data.loaded = true(flat property) - NOT
this.data.state.loading = false(nested object) - Simpler to check in templates:
!this.data.loaded
- Use
-
Template-Level Checks
- Loading state logic belongs in jqhtml template
- NOT in JavaScript:
if (!loaded) { this.show_spinner(); } - Declarative template:
<% if (!this.data.loaded) %>
-
Flag at END Only
- Set
this.data.loaded = trueas LAST line ofon_load() - NOT at start:
this.data.loading = truebefore fetch - Framework handles the "loading" state via empty
this.data
- Set
Recommendations
1. Update jqhtml Lifecycle Documentation
Add new section: "Loading State Pattern" (CRITICAL level)
Include:
- Complete correct pattern example (JS + template)
- Complete incorrect pattern example (what NOT to do)
- Explanation of why the pattern works (double-render)
- Common anti-patterns with warnings
- Comparison to React/Vue to help developers understand the difference
Location: After the lifecycle execution flow section, before the double-render pattern section.
2. Add to "Common Pitfalls" Section
Create new subsection: "Loading Pattern Anti-Patterns"
Include:
- ❌ NEVER call
this.render()manually inon_load() - ❌ NEVER use nested state objects (
this.data.state.loading) - ❌ NEVER set loading flags at START of
on_load() - ✅ DO set
this.data.loaded = trueat END - ✅ DO check
!this.data.loadedin template for loading state
3. Framework Warning Enhancement
Current: Runtime warning when setting properties other than this.data in on_load()
Suggested: Additional runtime warnings for:
- Calling
this.render()withinon_load()→ "WARNING: Manual render() call detected in on_load(). Remove it - framework handles re-rendering automatically." - Setting
this.data.loaded = trueat line 1 ofon_load()→ "WARNING: Loading flag set before data fetch. Move to END of on_load()."
These would align with RSpade's "fail loud" philosophy.
4. Example Components
Add reference implementations to framework:
- Simple_List_Component - Loads array of items, shows loading state
- Data_Grid_Component - Loads paginated data, handles empty state
- Form_Component - Loads initial data, handles submission
These serve as copy-paste templates for common use cases.
5. AI Assistant Training
Update CLAUDE.md (RSpade AI development guide) to include:
- Complete loading pattern documentation
- Anti-pattern warnings in "Common Pitfalls"
- Links to example components
Status: ✅ COMPLETED - This research report documents the changes made.
Impact Assessment
Severity: Medium-High
- Likelihood: High - Any developer with React/Vue background will likely implement this anti-pattern
- Detection Difficulty: Medium - Code appears to work, only causes issues during maintenance or edge cases
- Fix Difficulty: Low - Pattern is simple once understood
- Business Impact: Medium - Causes unpredictable behavior, violates framework principles, creates maintenance burden
Affected Audience
- Primary: AI assistants developing with RSpade/jqhtml (high likelihood of implementing anti-pattern)
- Secondary: Human developers new to jqhtml (moderate likelihood)
- Tertiary: Experienced jqhtml developers (low likelihood, but benefits from explicit documentation)
Documentation Priority
CRITICAL - This should be added to jqhtml documentation immediately because:
- High likelihood of implementation by new developers
- Violates core framework principles ("trust the framework")
- Creates silent failures (appears to work but causes issues)
- Simple to fix once pattern is understood
- Already causing issues in real-world usage
Conclusion
The loading state pattern issue represents a critical documentation gap in jqhtml's lifecycle documentation. While the framework correctly restricts what can be done in on_load() (only modify this.data, no DOM manipulation), it does not explicitly document the correct pattern for implementing loading states.
Developers with experience in other frameworks (React, Vue, Angular) will naturally implement familiar patterns that violate jqhtml's automatic re-rendering design. The framework should document this pattern explicitly with both correct and incorrect examples to prevent these anti-patterns.
The recommended fixes are straightforward:
- Add comprehensive "Loading State Pattern" section to jqhtml docs
- Add "Loading Pattern Anti-Patterns" to common pitfalls
- Consider runtime warnings for common mistakes
- Provide reference implementations
These changes will significantly improve the developer experience and reduce instances of this anti-pattern in production code.
Appendix: Conversation Timeline
- Initial Implementation: AI assistant implements DataGrid with manual
this.render()calls and nested state - Framework Update: jqhtml runtime warns about setting
this.stateinstead ofthis.data.state - First Correction: AI corrects to use
this.data.statebut still uses nested state and manual render - User Intervention: User demonstrates correct pattern with simple
this.data.loadedflag - Comprehension Check: AI explains pattern back to user to confirm understanding
- Documentation Review: User requests review of CLAUDE.md to identify documentation gaps
- Documentation Update: AI updates CLAUDE.md with comprehensive loading pattern documentation
- This Report: Documentation of the issue for jqhtml maintainers
Next Steps:
- ✅ Update CLAUDE.md (RSpade AI development guide) - COMPLETED
- ⏳ Review this report with jqhtml maintainers
- ⏳ Update official jqhtml lifecycle documentation
- ⏳ Consider runtime warning enhancements
- ⏳ Add reference component examples to framework