Lifecycle Patterns
Practical patterns and best practices for using lifecycle hooks effectively in your WildflowerJS components.
Advanced Lifecycle Patterns
External Library Integration
Use lifecycle hooks to integrate with external libraries:
wildflower.component('chart-widget', {
state: {
chartData: [10, 20, 30, 40, 50]
},
// Use watcher to track specific data changes
watch: {
chartData(newData) {
// Update chart when data changes
if (this._chart) {
this._chart.data.datasets[0].data = newData.slice()
this._chart.update('none')
}
}
},
init() {
// Initialize external chart library
const canvas = this.element.querySelector('canvas')
this._chart = new Chart(canvas, {
type: 'line',
data: {
datasets: [{
data: this.chartData.slice()
}]
}
})
},
destroy() {
// Clean up external library instance
if (this._chart) {
this._chart.destroy()
this._chart = null
}
}
})
Event Listener Management
Properly manage event listeners in lifecycle hooks:
wildflower.component('window-tracker', {
state: {
windowWidth: 0,
windowHeight: 0,
scrollY: 0
},
init() {
// Set initial values
this.updateWindowSize()
this.updateScrollPosition()
// Bind event handlers
this.handleResize = this.handleResize.bind(this)
this.handleScroll = this.handleScroll.bind(this)
// Add event listeners
window.addEventListener('resize', this.handleResize)
window.addEventListener('scroll', this.handleScroll)
},
destroy() {
// Remove event listeners to prevent memory leaks
window.removeEventListener('resize', this.handleResize)
window.removeEventListener('scroll', this.handleScroll)
},
handleResize() {
this.updateWindowSize()
},
handleScroll() {
this.updateScrollPosition()
},
updateWindowSize() {
this.windowWidth = window.innerWidth
this.windowHeight = window.innerHeight
},
updateScrollPosition() {
this.scrollY = window.scrollY
}
})
Async Data Loading
A common pattern is fetching data from an API with loading, error, and success states. Use async methods with data-show to display each state:
<div data-component="api-data-loader">
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">Async Data Loading Demo</h5>
<div class="mb-2">
<button class="btn btn-primary btn-sm" data-action="loadUsers">Load Users</button>
<button class="btn btn-secondary btn-sm ms-1" data-action="clear">Clear</button>
</div>
<div data-show="loading" class="text-center py-3">
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
<span class="ms-2">Loading...</span>
</div>
<div data-show="hasError" class="alert alert-danger py-2 mb-2">
Error: <span data-bind="error"></span>
</div>
<div data-show="isEmpty" class="text-muted">
No data loaded. Click "Load Users" to fetch from an API.
</div>
<ul class="list-group" data-show="hasData" data-list="users">
<template>
<li class="list-group-item">
<strong data-bind="name"></strong>
<small class="text-muted ms-2" data-bind="email"></small>
</li>
</template>
</ul>
</div>
</div>
</div>
wildflower.component('api-data-loader', {
state: {
users: [],
loading: false,
error: null
},
computed: {
hasError() { return this.error !== null },
isEmpty() { return !this.loading && !this.error && this.users.length === 0 },
hasData() { return this.users.length > 0 }
},
async loadUsers() {
this.loading = true
this.error = null
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
if (!response.ok) throw new Error('HTTP ' + response.status)
this.users = await response.json()
} catch (err) {
this.error = err.message
} finally {
this.loading = false
}
},
clear() {
this.users = []
this.error = null
}
})
Key points:
- Use
asyncon the method; WildflowerJS handles async methods naturally - Set
loading = truebefore the request,falseinfinallyto guarantee cleanup - Use computed properties (
hasError,isEmpty,hasData) withdata-showto display the correct state - Catch errors and store the message so users see what went wrong
Lifecycle Best Practices
✅ Do
- Initialize external libraries in
init() - Set up event listeners in
init() - Clean up resources in
destroy() - Use
onUpdate()for state-based side effects - Bind event handlers to preserve
thiscontext
❌ Don't
- Modify state in
onUpdate()(causes infinite loops!) - Forget to clean up timers and intervals
- Leave event listeners attached after destruction
- Perform heavy operations in
onUpdate() - Access DOM elements before
init()
Common Lifecycle Patterns
| Use Case | Hook | Pattern |
|---|---|---|
| Measure initial DOM | beforeInit() |
Capture dimensions before bindings modify DOM |
| API Data Loading | init() |
Fetch initial data, set loading states |
| Timer/Interval Setup | init() + destroy() |
Start in init, clear in destroy |
| Global Event Listeners | init() + destroy() |
Add in init, remove in destroy |
| Capture scroll position | beforeUpdate() |
Save scroll position before DOM changes |
| DOM operations after update | onUpdate() |
Restore scroll, trigger animations |
| Track specific property | watch |
Use watchers for specific property changes with old/new values |
| Save state before removal | beforeDestroy() |
Persist data, save drafts before cleanup |
| Error Boundary | onError() |
Catch errors, show fallback UI, log to services |
| Retry After Error | onReset() + resetError() |
Clear error state and retry failed operations |
| React to Store Changes | subscribe + onStoreUpdate() |
Centralized handling of store path changes |