f/cfml Edited 10 days ago

CBWire: Computed vs Data Properties

CBWire (ColdFusion Livewire) follows patterns similar to Laravel Livewire. Here's a breakdown of when to use each:


Data Properties

Data properties are the reactive state of your component. They are:

  • Persisted between requests (stored in component state)
  • Two-way bindable via wire:model
  • Automatically available in your view
  • Included in the component's hydration/dehydration lifecycle
// MyComponent.cfc
component extends="cbwire.models.Component" {

    data = {
        "firstName": "",
        "lastName": "",
        "items": [],
        "searchTerm": ""
    };

    function save() {
        var user = userService.create(
            firstName = data.firstName,
            lastName  = data.lastName
        );
    }

}
<!-- MyComponent.cfm -->
<input type="text" wire:model="firstName" placeholder="First Name">
<input type="text" wire:model="lastName" placeholder="Last Name">
<button wire:click="save">Save</button>

Use data properties when you need to:

  • Bind form inputs with wire:model
  • Store user input or selections
  • Track state that changes due to user interaction
  • Persist values across multiple requests in the component lifecycle

Computed Properties

Computed properties are derived values calculated on the fly. They are:

  • Not stored in component state
  • Cached per request (the function only runs once per request even if called multiple times)
  • Read-only (cannot be bound with wire:model)
  • Ideal for expensive operations or derived data
  • Registered via the computed annotation after the function name
  • Cache can be bypassed by passing false as the first argument
// ProductListComponent.cfc
component extends="cbwire.models.Component" {

    data = {
        "searchTerm": "",
        "categoryId": 0,
        "sortBy": "name"
    };

    // computed annotation registers this as a computed property
    function getFilteredProducts() computed {
        return productService.search(
            term       = data.searchTerm,
            categoryId = data.categoryId,
            sortBy     = data.sortBy
        );
    }

    function getTotalCount() computed {
        // Reuses cached getFilteredProducts() - no second DB call!
        return getFilteredProducts().len();
    }

    function getHasResults() computed {
        return getTotalCount() > 0;
    }

    function getFreshCount() computed {
        // Pass false to bypass cache and force a fresh DB call
        return getFilteredProducts( false ).len();
    }

}
<!-- ProductListComponent.cfm -->
<input type="text" wire:model.live.debounce.300ms="searchTerm" placeholder="Search...">

<cfif getHasResults()>
    <p>Showing #getTotalCount()# results</p>
    <cfloop array="#getFilteredProducts()#" item="product">
        <div class="product-card">
            <h3>#product.getName()#</h3>
            <p>#product.getPrice()#</p>
        </div>
    </cfloop>
<cfelse>
    <p>No products found.</p>
</cfif>

<!-- Force a fresh result outside of the cache -->
<p>Live count: #getFilteredProducts( false ).len()#</p>

Use computed properties when you need to:

  • Derive data from existing data properties
  • Run database queries based on current state
  • Avoid storing redundant data (e.g. getFullName() derived from firstName + lastName)
  • Call the same expensive operation multiple times in a template without re-running it
  • Need a fresh result mid-render by passing false to bypass the cache

Side-by-Side Example

component extends="cbwire.models.Component" {

    data = {
        "firstName": "John",   // DATA: stored in state, bindable
        "lastName":  "Smith",  // DATA: stored in state, bindable
        "items": []            // DATA: stored in state, bindable
    };

    // COMPUTED: derived, cached, not stored in state
    function getFullName() computed {
        return data.firstName & " " & data.lastName;
    }

    // COMPUTED: derived from a data property
    function getItemCount() computed {
        return data.items.len();
    }

    // COMPUTED: expensive operation, cached per request
    function getRecentOrders() computed {
        return orderService.getRecentOrders( userId = auth().getUserId() );
    }

}
<!-- Two-way binding works on DATA properties -->
<input wire:model="firstName">
<input wire:model="lastName">

<!-- Computed properties are read-only, available in the template -->
<p>Hello, #getFullName()#!</p>
<p>You have #getItemCount()# items</p>
<p>Recent orders: #getRecentOrders().len()#</p>

<!-- Bypass cache to get a fresh result -->
<p>Fresh order count: #getRecentOrders( false ).len()#</p>

Quick Decision Guide

Scenario Use
Binding a form inputData
Storing user selections/togglesData
Deriving a value from other dataComputed
Running a DB query based on filtersComputed
Value changes via user interactionData
Value that would be redundant to storeComputed
Need wire:model two-way bindingData
Expensive logic called multiple times in viewComputed (cached)
Need a guaranteed fresh result mid-renderComputed with false

The biggest practical benefit of computed properties in CBWire is the per-request caching - if your template calls getFilteredProducts() five times, the database query only runs once. When you genuinely need a fresh result, simply pass false to bypass the cache. This makes computed properties ideal for wrapping service calls driven by your data property filters, without worrying about redundant queries.

Comments

No comments yet. Log in to start the conversation.

f/cfml

Adobe ColdFusion, Lucee, and BoxLang

Created Feb 13, 2026

1
Member

Moderators
u/rob