---
title: Applying changes
description: >-
  Learn how to keep checkout UI extension changes responsive and within rate
  limits.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/checkout/extension-performance/applying-changes
  md: >-
    https://shopify.dev/docs/apps/build/checkout/extension-performance/applying-changes.md
---

# Applying changes

[Checkout UI extensions](https://shopify.dev/docs/api/checkout-ui-extensions) can use [checkout APIs](https://shopify.dev/docs/api/checkout-ui-extensions/latest/target-apis) to make changes, such as updating cart lines, cart metafields, order notes, and privacy consent. Apply each change only after the buyer's intent is clear and when the new value is different. When possible, apply all changes at the same time. Before calling mutation APIs, check [cart instructions](https://shopify.dev/docs/api/checkout-ui-extensions/apis/cart-instructions) to confirm that the change is allowed.

**Rate limits may apply:**

Excessive changes may rate limit the extension and prevent it from applying more changes to the checkout. Rate-limited extensions can't block the buyer journey.

***

## Debounce user-driven changes

Debounce API calls that respond to user input. Calling an API after every keystroke or interaction creates unnecessary work and can degrade performance.

## Debouncing input changes

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
import {
  useCallback,
  useRef,
  useState,
} from 'preact/hooks';


function Extension() {
  const [value, setValue] = useState('');
  const timerRef = useRef(null);


  const handleInput = useCallback((event) => {
    const newValue = event.currentTarget.value;
    setValue(newValue);


    // Clear any pending update.
    if (timerRef.current) {
      clearTimeout(timerRef.current);
    }


    // Wait for the buyer to stop typing before applying the change.
    timerRef.current = setTimeout(() => {
      if (
        !shopify.instructions.value.metafields
          .canSetCartMetafields
      ) {
        return;
      }


      void shopify.applyMetafieldChange({
        type: 'updateCartMetafield',
        metafield: {
          namespace: '$app:preferences',
          key: 'gift-note',
          type: 'single_line_text_field',
          value: newValue,
        },
      });
    }, 500);
  }, []);


  return (
    <s-text-field
      label="Gift note"
      value={value}
      onInput={handleInput}
    />
  );
}


export default function extension() {
  render(<Extension />, document.body);
}
```

***

## Avoid changes in render loops

In code that runs on every render or in response to reactive state changes, don't apply changes to checkout state without appropriate guards. If a buyer action changes extension state, then compare the new value with the last value that your extension requested before calling the API.

## Avoiding render-loop changes

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
import {
  useCallback,
  useRef,
  useState,
} from 'preact/hooks';


function Extension() {
  const [includeGiftWrap, setIncludeGiftWrap] =
    useState(false);
  const lastRequestedValue = useRef('false');
  const canSetCartMetafields =
    shopify.instructions.value.metafields
      .canSetCartMetafields;


  const handleGiftWrapChange = useCallback((event) => {
    const checked = event.currentTarget.checked;
    const value = checked ? 'true' : 'false';
    setIncludeGiftWrap(checked);


    if (lastRequestedValue.current === value) {
      return;
    }


    lastRequestedValue.current = value;
    void shopify.applyMetafieldChange({
      type: 'updateCartMetafield',
      metafield: {
        namespace: '$app:preferences',
        key: 'include-gift-wrap',
        type: 'boolean',
        value,
      },
    });
  }, []);


  // ❌ Bad: runs during render when `includeGiftWrap` is true.
  // if (includeGiftWrap) {
  //   shopify.applyMetafieldChange({...});
  // }


  if (!canSetCartMetafields) {
    return null;
  }


  return (
    <s-checkbox
      label="Gift wrap"
      checked={includeGiftWrap}
      onChange={handleGiftWrapChange}
    />
  );
}


export default function extension() {
  render(<Extension />, document.body);
}
```

***

## React to state changes selectively

When your extension reads checkout state, such as the shipping address or cart lines, apply changes only when the specific data your extension needs has changed. Don't apply a change for every state update. The following example synchronizes the country and province as one value, and the guard prevents another request when that value stays the same.

## Reacting selectively to state changes

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
import {useCallback, useEffect, useRef} from 'preact/hooks';


function Extension() {
  const lastRegionRef = useRef(null);
  const shippingAddress = shopify.shippingAddress?.value;
  const countryCode = shippingAddress?.countryCode;
  const provinceCode = shippingAddress?.provinceCode;
  const region = countryCode
    ? [countryCode, provinceCode].filter(Boolean).join('-')
    : null;
  const canSetCartMetafields =
    shopify.instructions.value.metafields
      .canSetCartMetafields;


  const syncShippingRegion = useCallback((newRegion) => {
    return shopify.applyMetafieldChange({
      type: 'updateCartMetafield',
      metafield: {
        namespace: '$app:preferences',
        key: 'shipping-region',
        type: 'single_line_text_field',
        value: newRegion,
      },
    });
  }, []);


  useEffect(() => {
    if (
      !region ||
      region === lastRegionRef.current ||
      !canSetCartMetafields
    ) {
      return;
    }


    async function syncRegion() {
      const result = await syncShippingRegion(region);


      if (result.type !== 'error') {
        lastRegionRef.current = region;
      }
    }


    void syncRegion();
  }, [canSetCartMetafields, region, syncShippingRegion]);


  return <s-text>Tracking shipping region</s-text>;
}


export default function extension() {
  render(<Extension />, document.body);
}
```

***

## Apply changes at the same time

If you need to make multiple changes, then apply them in parallel with `Promise.all` instead of awaiting each one sequentially. This reduces the time spent applying changes, avoids delays between calls, and makes the extension less likely to trip rate limits.

Synchronize all changes that can be applied from the same buyer intent. For example, use parallel calls when a buyer action updates a cart metafield and sets an order note.

## Applying changes at the same time

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';


function Extension() {
  const {metafields, notes} = shopify.instructions.value;


  async function saveGiftPreferences() {
    const [metafieldResult, noteResult] =
      await Promise.all([
        shopify.applyMetafieldChange({
          type: 'updateCartMetafield',
          metafield: {
            namespace: '$app:preferences',
            key: 'include-gift',
            type: 'boolean',
            value: 'true',
          },
        }),
        shopify.applyNoteChange({
          type: 'updateNote',
          note: 'Please include a gift receipt.',
        }),
      ]);


    if (
      metafieldResult.type === 'error' ||
      noteResult.type === 'error'
    ) {
      console.error('Gift preferences failed to save');
    }
  }


  if (
    !metafields.canSetCartMetafields ||
    !notes.canUpdateNote
  ) {
    return null;
  }


  return (
    <s-button onClick={saveGiftPreferences}>
      Save gift preferences
    </s-button>
  );
}


export default function extension() {
  render(<Extension />, document.body);
}
```

***

## Handle errors gracefully

Don't retry aggressively. When a change returns an error, let the buyer retry manually. Consider retrying automatically only if the failure is due to a network error.

***
