Skip to content
Twilight React Playground
ثيم رائدaren

useCoupon

hookBeginnerserverbrowserlive demo

Applies or removes a discount coupon on the shopper's cart through the Salla SDK, with loading and error state.

import { useCoupon, UseCouponResult } from '@salla.sa/twilight-theme-engine/hooks/useCoupon';

In plain words

Lets a shopper type a discount code. apply('WELCOME10') asks Salla to add it to the cart and resolves true or false. remove() takes it off.

While a request is on its way loading is true, and when one fails error holds the message to show. value is the code this component applied.

Signature

function useCoupon(): UseCouponResult

type UseCouponResult = UseApply<string>;
// {
//   value: string | null;
//   apply: (code: string) => Promise<boolean>;
//   remove: () => Promise<boolean>;
//   loading: boolean;  error: string | null;
// }

Try it live

Applies a coupon to your own guest cart on the demo store. Salla creates that cart if you don't have one yet.Try this: clear the code and apply (the hook refuses it before any request), then apply a made-up code (the store refuses it), then Remove.
Real requests to the demo store
Storefront canvas · ar · RTL

value: null · loading: false · error: null

Controls
What a theme writes
import { useState } from 'react';
import { useCoupon } from '@salla.sa/twilight-theme-engine/hooks/useCoupon';

export function CouponBox() {
  const { apply, loading, error } = useCoupon();
  const [code, setCode] = useState('WELCOME10');
  return (
    <form
      onSubmit={async (event) => {
        event.preventDefault();
        if (await apply(code)) setCode('');
      }}
    >
      <input value={code} onChange={(event) => setCode(event.target.value)} />
      <button disabled={loading}>Apply</button>
      {error && <p role="alert">{error}</p>}
    </form>
  );
}

Example

app/components/cart/CouponForm.tsx
import { useState } from 'react';
import { useCoupon } from '@salla.sa/twilight-theme-engine/hooks/useCoupon';

export function CouponForm() {
  const { apply, loading, error } = useCoupon();
  const [code, setCode] = useState('');

  return (
    <form
      onSubmit={async (event) => {
        event.preventDefault();
        if (await apply(code)) setCode('');
      }}
    >
      <input value={code} onChange={(event) => setCode(event.target.value)} placeholder="Coupon" />
      <button type="submit" disabled={loading}>
        {loading ? 'Applying…' : 'Apply'}
      </button>
      {error && <p role="alert">{error}</p>}
    </form>
  );
}

How it behaves

  • apply(code) trims the code and calls Salla.cart.addCoupon(code); remove() calls Salla.cart.deleteCoupon(). An empty or whitespace-only code throws "Please enter a coupon code" before any request, which shows up as error.

  • It is built on two useAsyncFn calls. loading is true while either runs, and error is the message of the apply error, else the remove error.

  • State belongs to each component that calls the hook; two coupon boxes do not share it.

  • Salla's SDK creates a cart for a guest who has none when a coupon is applied.

  • The engine's CartSummary uses it for its coupon field.

Gotchas

  • value starts as null and never reads the coupon already on the cart, and it is null again after a successful remove(). value ?? cart.coupon (the engine's CartSummary pattern) therefore shows the removed coupon again until the cart data refreshes.

  • apply and remove resolve true unless what was thrown is an Error. Without window.Salla (for example during a test), the optional call does nothing and they still resolve true, with value set.

  • The hook does not refresh cart totals. Update the cart from the SDK cart events or your cart query after it resolves.

Related

Source and docs