# State and data (/docs/concepts/state-and-data)



All app state (config, products, cart, user, navigation) lives in a single Zustand-based store. You never touch the store directly. `@gauntlet/state` exposes it through hooks, and blocks read and write everything through them. This keeps blocks decoupled from where data actually comes from: the same hook call is backed by config, Shopify APIs, or Appbrew services as appropriate, with caching handled for you.

## The hook families [#the-hook-families]

The names below are the ones you will use most, grouped by what they cover.

**Blocks** — a block's own config, resolved through the layers described in [Config and theme](/docs/concepts/config-and-theme):

* `useBlock(screenId, componentId, instanceId)` — the block's raw config entry
* `useBlockSettings(block)` — merged `source` and `options` (globals + instance)
* `useBlockStyle(block)` — merged style (theme + variant + instance)

**Products:**

* `useProduct`, `useProductByHandle`, `useProductsByHandles`

**Cart and checkout:**

* `useCart`, `useAddToCart`, `useCheckout`

**User:**

* `useUser` — the signed-in customer and authentication state

**Settings:**

* `useSettings` — the app's global settings object
* `useGlobalSettings` — global defaults for block types
* `useIntegrationSettings` — your integration's config values (see [The integrations model](/docs/concepts/integrations-model))

**Navigation:**

* `useRoute` — the current route
* `useLink` — navigate to a screen, product, collection, or URL from config-style links

**Shopify queries and metafields:**

* `useShopifyQuery` — run a raw Storefront GraphQL query
* `useMetafieldQuery`, `useMultipleMetafields` — read metafields from products and other resources

**Analytics:**

* `useAnalytics` — fire events to every registered tracker

## Type the result, null-check before use [#type-the-result-null-check-before-use]

Hooks that fetch remote data return `null` (or `undefined`) until the data arrives, and query hooks are generic: you declare the shape you expect. Both rules together look like this:

```tsx
import { useShopifyQuery } from '@gauntlet/state'

interface FeaturedProductResult {
  product: {
    title: string
    handle: string
  } | null
}

const query = `
  query {
    product(handle: "featured-hoodie") {
      title
      handle
    }
  }
`

export function FeaturedProduct() {
  const data = useShopifyQuery<FeaturedProductResult>(query)

  if (!data?.product) {
    return null
  }

  return <Text>{data.product.title}</Text>
}
```

The pattern generalizes to every data hook: define an interface for the result, assert or parameterize the hook with it, and guard before rendering. Rendering `null` while data loads is correct. A block that assumes data is present crashes the screen for users on slow networks.

<Callout type="info" title="Arrays too">
  The same discipline applies to lists: check the array exists and has items before mapping over it. Config-driven data means any field can be absent on any given app.
</Callout>

## Writing state [#writing-state]

Mutations go through the same hooks: `useAddToCart` returns a function that adds line items, `useCheckout` starts checkout, and so on. Fire-and-forget from event handlers; the store updates and every subscribed block re-renders reactively.

## Beyond hooks: actions and events [#beyond-hooks-actions-and-events]

Hooks are how your UI reads and writes. **Actions** are the layer underneath: `cart:add`, `checkout:begin`, `coupon:apply` and friends are named operations that anyone can intercept through the [`app` extension host](/docs/build/extending-app-behavior) from `@gauntlet/brewery` (platform 0.27.0 and later). `useAddToCart` runs `cart:add` for you.

That matters in two directions. If you are extending someone's app (a partner integration), it is how you change behavior without touching their code. If you are calling an action yourself, note that it returns a `Result` instead of throwing, so a blocked operation hands you a reason to show the user.

**On 0.26 and earlier:** the action layer isn't available. Hooks call the built-in operations directly, and a package can't intercept them; behavior changes need a change in app code.

There is also `onConfigReady(cb)` for running setup once app config has landed. See [Extending app behavior](/docs/build/extending-app-behavior).

## Full reference [#full-reference]

The complete hook catalog with signatures lives at [the @gauntlet/state reference](/docs/reference/packages/state).
