Skip to content
Backlit
Esc
navigateopen⌘Jpreview
On this page

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.

Was this page helpful?