RizzyUI

Lifecycle and Initialization

Have you ever wondered why a prop isn’t available yet in your script, or why a cleanup function didn’t fire when a component disappeared? That’s a lifecycle issue. This page shows you how RizzyUI ensures predictable initialization and teardown by explaining the sequence of events when an interactive component loads.

The Page Initialization Lifecycle

When a page with RizzyUI and Alpine components loads, a specific sequence of events takes place. Understanding this flow helps you know where to place your code.

Lifecycle Flow at a Glance

[DOM Ready] → [RizzyUI Script] → [Alpine Init] 
    ↓
[alpine:init] → [DOM Scan] → [init()] → [alpine:initialized]

Detailed Steps

  1. DOM Ready: The browser parses the HTML from the Blazor server.
  2. RizzyUI Script Execution: The main rizzyui.js or rizzyui-csp.js script runs and registers your co-located modules with async-alpine.
  3. Alpine Initialization: Alpine.js starts its own initialization process.
  4. alpine:init Event: Alpine fires this event. This is the correct place to register global extensions like Alpine.store().
  5. DOM Scan: Alpine scans the DOM for components (elements with x-data).
  6. Component init(): For each component, Alpine loads its async module, creates the reactive state, and executes the init() method.
  7. alpine:initialized Event: After all initial components are ready, Alpine fires this event.

The init() Method: Your Component's Entry Point

The init() method in your co-located .razor.js file is the most important lifecycle hook. It runs once per component instance and is the designated place for all setup logic.

MyComponent.razor
@attribute [RzAlpineCodeBehind]

<RzAlpineComponent For="this" Name="myComponent" Props="@(new { initialMessage = "Hello!" })">
  <p x-text="message"></p>
  <RzDialog Title="My Modal" x-ref="myModal" />
</RzAlpineComponent>
MyComponent.razor.js
export default () => ({
  message: '',
  modal: null,

  init() {
    // 1. Get initial data from Blazor
    const props = Rizzy.props(this.$el);
    this.message = props.initialMessage || 'Welcome!';

    // 2. Set up watchers or effects
    this.$watch('message', (newValue) => console.log(`Message changed to: ${newValue}`));

    // 3. Get references to other components
    this.modal = Rizzy.$data(this.$refs.myModal);
  }
});

Handling Asynchronous Setup

The init() method can also be used for asynchronous tasks, like fetching initial data from an API.

Source
init() {
  fetch('/api/user')
    .then(res => res.json())
    .then(data => this.user = data);
}
Source
init() {
  this.$nextTick(() => {
    // This code runs after the initial render is complete.
    console.log(this.$refs.myElement.offsetHeight);
  });
}

x-init vs. init()

You may see x-init used in general Alpine.js documentation. In RizzyUI, you should always prefer the init() method in your code-behind for cleanliness, organization, and security.

❌ Not the RizzyUI Way

Source
<div x-data="{ message: '' }" x-init="message = 'Hi'"></div>

✅ The RizzyUI Way

Razor
<RzAlpineComponent Name="example">
  <div x-text="message"></div>
</RzAlpineComponent>
JavaScript
export default () => ({
  message: '',
  init() { this.message = 'Hi'; }
});

destroy(): Cleaning Up Your Components

Just as init() runs on setup, you can provide a destroy() method that Alpine calls when a component is removed from the DOM. This is crucial for preventing memory leaks, especially for components swapped in and out by HTMX.

Common Cleanup Tasks

  • Removing intervals or timeouts (clearInterval, clearTimeout).
  • Detaching global event listeners (e.g., window.removeEventListener).
  • Disposing of third-party library instances (e.g., Chart.js, Mapbox).
  • Resetting state in other RizzyUI components that were being controlled.
LifecycleLogger.razor.js
export default () => ({
  seconds: 0,
  intervalId: null,

  handleResize() {
    console.log('Window resized!');
  },

  init() {
    console.log('Component initialized!');
    this.intervalId = setInterval(() => {
      this.seconds++;
    }, 1000);

    window.addEventListener('resize', this.handleResize);
  },

  destroy() {
    if (this.intervalId) {
      clearInterval(this.intervalId);
    }
    window.removeEventListener('resize', this.handleResize);
    
    console.log('Component destroyed and cleaned up.');
  }
});

Next Steps

Understanding the lifecycle gives you control over *when* your code executes. By pairing init() with lazy loading, you ensure components only pay their setup cost when needed, improving both performance and maintainability.

Next, we'll explore how to use RzAlpineComponent's LoadStrategy parameter for Optimizing with Lazy Loading.