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
computedannotation after the function name - Cache can be bypassed by passing
falseas 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 fromfirstName+lastName) - Call the same expensive operation multiple times in a template without re-running it
- Obtain a fresh result mid-render by passing
falseto 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
| Scenario | Use |
|---|---|
| Binding a form input | Data |
| Storing user selections or toggles | Data |
| Deriving a value from other data | Computed |
| Running a database query based on filters | Computed |
| Value changes via user interaction | Data |
| Value that would be redundant to store | Computed |
Need wire:model two-way binding | Data |
| Expensive logic called multiple times in a view | Computed (cached) |
| Need a guaranteed fresh result mid-render | Computed 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.