Lifecycle Patterns

Practical patterns and best practices for using lifecycle hooks effectively in your WildflowerJS components.

💡 Prerequisite: This page builds on the Lifecycle Hooks reference. Make sure you're familiar with the core hooks before exploring these patterns.

Advanced Lifecycle Patterns

External Library Integration

Most libraries need a real DOM element at setup time and a teardown call when that element leaves. init() and destroy() are exactly those two moments:

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
    }
})
Live Preview

Key points:

  • Use async on the method; WildflowerJS handles async methods naturally
  • Set loading = true before the request, false in finally to guarantee cleanup
  • Use computed properties (hasError, isEmpty, hasData) with data-show to 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 this context
❌ 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