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
- DOM Ready: The browser parses the HTML from the Blazor server.
- RizzyUI Script Execution: The main
rizzyui.jsorrizzyui-csp.jsscript runs and registers your co-located modules withasync-alpine. - Alpine Initialization: Alpine.js starts its own initialization process.
alpine:initEvent: Alpine fires this event. This is the correct place to register global extensions likeAlpine.store().- DOM Scan: Alpine scans the DOM for components (elements with
x-data). - Component
init(): For each component, Alpine loads its async module, creates the reactive state, and executes theinit()method. alpine:initializedEvent: After all initial components are ready, Alpine fires this event.
If you try to register a store or directive after the alpine:init event has fired, Alpine won’t see it. Always register global helpers inside an alpine:init event listener.
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.
@attribute [RzAlpineCodeBehind]
<RzAlpineComponent For="this" Name="myComponent" Props="@(new { initialMessage = "Hello!" })">
<p x-text="message"></p>
<RzDialog Title="My Modal" x-ref="myModal" />
</RzAlpineComponent>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.
init() {
fetch('/api/user')
.then(res => res.json())
.then(data => this.user = data);
}Deferring DOM Access with $nextTick
The init() method runs before child elements inside conditionals (like x-if) are rendered. If you need to access a DOM element that might not exist yet, use this.$nextTick() to wait for Alpine's next DOM update.
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.
CSP Violation Example
Using x-init with inline expressions in CSP mode will trigger browser errors like: "Refused to execute inline script because it violates the Content Security Policy directive."
❌ Not the RizzyUI Way
<div x-data="{ message: '' }" x-init="message = 'Hi'"></div>✅ The RizzyUI Way
<RzAlpineComponent Name="example">
<div x-text="message"></div>
</RzAlpineComponent>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.
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.