Zone
Define part of a view that can refresh on its own.
A zone defines a named part of a view that the client can request again without a request for the complete view. Use it for content that changes independently.
The zone name must be unique in its view. It can contain letters, numbers, underscores, and hyphens. A zone cannot contain another zone.
API
zone(name, resolver)
zone(name, atoms, resolver)
The resolver can be synchronous or asynchronous. It returns one block or an array of blocks. Backlit adds the current resource and view address to the protocol output.
Register filters, a paginator, or a sorter in atoms. Read ctx.parse(zone)
and place bindings from zone.state. See Search state.
Basic zone
Pass a unique name and a resolver that produces the zone content.
import {
defineResource,
fields,
widget,
zone,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const revenue = fields.number('revenue', {
format: 'currency',
currency: 'USD',
})
orders.view('overview', () =>
zone('revenue', () =>
widget()
.setTitle('Total revenue')
.setStat({ value: 48_120, field: revenue })
)
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "zone",
"name": "revenue",
"resource": "orders",
"view": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Total revenue",
"stat": {
"value": 48120,
"field": {
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
},
"delta": "absolute"
}
}
]
}
}
]
}
}
}Async content
Use an async resolver when the zone must fetch or compute its content.
import {
defineResource,
fields,
widget,
zone,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const revenue = fields.number('revenue', {
format: 'currency',
currency: 'USD',
})
orders.view('overview', () =>
zone('revenue', async () => {
const total = await Promise.resolve(48_120)
return widget()
.setTitle('Total revenue')
.setStat({ value: total, field: revenue })
})
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "zone",
"name": "revenue",
"resource": "orders",
"view": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Total revenue",
"stat": {
"value": 48120,
"field": {
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
},
"delta": "absolute"
}
}
]
}
}
]
}
}
}Multiple content items
Return an array when one zone must refresh several related blocks together.
import {
defineResource,
fields,
widget,
zone,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const revenue = fields.number('revenue', {
format: 'currency',
currency: 'USD',
})
const count = fields.number('count', { format: 'integer' })
const rate = fields.number('rate', {
format: 'percent',
decimals: 1,
})
orders.view('overview', () =>
zone('sales', () => [
widget()
.setTitle('Total revenue')
.setStat({ value: 48_120, field: revenue }),
widget()
.setTitle('Orders')
.setStat({ value: 1_204, field: count }),
widget()
.setTitle('Refund rate')
.setStat({ value: 0.018, field: rate }),
])
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "zone",
"name": "sales",
"resource": "orders",
"view": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Total revenue",
"stat": {
"value": 48120,
"field": {
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
},
"delta": "absolute"
}
},
{
"kind": "widget",
"title": "Orders",
"stat": {
"value": 1204,
"field": {
"kind": "number",
"name": "count",
"format": "integer",
"label": ""
},
"delta": "absolute"
}
},
{
"kind": "widget",
"title": "Refund rate",
"stat": {
"value": 0.018,
"field": {
"kind": "number",
"name": "rate",
"format": "percent",
"decimals": 1,
"label": ""
},
"delta": "absolute"
}
}
]
}
}
]
}
}
}Zone in a section
A zone can appear in a section or another block that accepts content. A partial zone response does not include the section because it is already on the screen.
import {
defineResource,
fields,
section,
widget,
zone,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const revenue = fields.number('revenue', {
format: 'currency',
currency: 'USD',
})
orders.view('overview', () =>
section('Sales').setContent([
zone('revenue', () =>
widget()
.setTitle('Total revenue')
.setStat({ value: 48_120, field: revenue })
),
])
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "section",
"title": "Sales",
"slots": {
"content": [
{
"kind": "zone",
"name": "revenue",
"resource": "orders",
"view": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Total revenue",
"stat": {
"value": 48120,
"field": {
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
},
"delta": "absolute"
}
}
]
}
}
]
}
}
]
}
}
}Multiple zones
A view can have several zones. Each zone has its own name and refresh boundary.
import {
defineResource,
fields,
section,
widget,
zone,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const revenue = fields.number('revenue', {
format: 'currency',
currency: 'USD',
})
const count = fields.number('count', { format: 'integer' })
orders.view('overview', () =>
section('Sales').setContent([
zone('revenue', () =>
widget()
.setTitle('Total revenue')
.setStat({ value: 48_120, field: revenue })
),
zone('order-count', () =>
widget()
.setTitle('Orders')
.setStat({ value: 1_204, field: count })
),
])
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "section",
"title": "Sales",
"slots": {
"content": [
{
"kind": "zone",
"name": "revenue",
"resource": "orders",
"view": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Total revenue",
"stat": {
"value": 48120,
"field": {
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
},
"delta": "absolute"
}
}
]
}
},
{
"kind": "zone",
"name": "order-count",
"resource": "orders",
"view": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Orders",
"stat": {
"value": 1204,
"field": {
"kind": "number",
"name": "count",
"format": "integer",
"label": ""
},
"delta": "absolute"
}
}
]
}
}
]
}
}
]
}
}
}Partial requests
app.renderZone runs the view resolver again to find the zone. It runs the
requested zone resolver and skips sibling zone resolvers. Put expensive queries
inside zone resolvers so sibling refreshes can skip them. Keep view composition
free of mutations because both full and partial requests execute it.
A zone must be in an authored content slot. Do not return a zone from another
zone resolver. A zone inside a parameterized view carries the view arguments.
Use ctx.refreshZones(['name']) after an action to refresh a named zone.