All Articles

CBWire: Computed vs Data Properties

CBWire uses data properties for reactive state that persists between requests and supports two-way binding, making them ideal for form inputs and user interactions. Computed properties are read-only, derived values that are automatically cached per request, perfect for expensive operations like database queries or calculations that depend on data properties.

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 within the component lifecycle

Computed Properties

Computed properties are derived values calculated on demand. They are:

  • Not stored in component state
  • Cached per request (the function runs only 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 database call!
        return getFilteredProducts().len();
    }

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

    function getFreshCount() computed {
        // Pass false to bypass cache and force a fresh database 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 (for example, getFullName() derived from firstName + lastName)
  • Call the same expensive operation multiple times in a template without re-running it
  • Obtain 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 only on DATA properties -->
<input wire:model="firstName">
<input wire:model="lastName">

<!-- Computed properties are read-only and 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

ScenarioUse
Binding a form inputData
Storing user selections or togglesData
Deriving a value from other dataComputed
Running a database 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 a viewComputed (cached)
Need a guaranteed fresh result mid-renderComputed with false

Key Takeaway

The biggest practical benefit of computed properties in CBWire is per-request caching. If your template calls getFilteredProducts() five times, the database query runs only once. When you 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.