Discounts
Discount and coupon code management components for e-commerce applications.
Purpose
This library provides headless, flexible components for managing discounts, coupon codes, and promotional offers in e-commerce applications. It enables applying coupon codes, displaying active discounts, and removing promotional codes from orders.
Features
- Coupon code application and validation
- Active discount display with promotion details
- Discount removal functionality
- Headless component architecture for full customization
- Error handling and loading states
- Accessibility-first design
Installation
- npm
- Yarn
npm install @haus-storefront-react/discounts
yarn add @haus-storefront-react/discounts
Note: This is not a public package. Contact the Haus Tech Team for access.
API Reference
CouponCode.Root
The main container component that provides coupon code context to all child components. Uses a render function pattern to pass context values.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | ChildrenProps<UseCouponCodeReturn> | No | - | Render function that receives the coupon code context |
CouponCode.Input
Text input component for entering coupon codes. Automatically handles Enter key to apply the coupon code. Disables during loading state and supports all standard HTML input attributes.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
placeholder | string | No | - | Placeholder text |
disabled | boolean | No | false | Disabled state |
Behavioral Notes:
- Enter key triggers coupon code application
- Automatically disabled when
isLoadingis true - Controlled component managed by internal context
CouponCode.ApplyButton
Button component to apply the entered coupon code. Automatically disabled when no code is entered or while loading.
Behavioral Notes:
- Automatically disabled when
isLoadingis true or whencodeis empty - Children receive
{ isLoading, error }as render props
CouponCode.RemoveButton
Button component to remove a specific coupon code from the order.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
couponCode | string | Yes | - | The coupon code to remove |
asChild | boolean | No | false | Use a custom component instead of the default button |
Behavioral Notes:
- Automatically disabled when
isLoadingis true - Triggers removal of the specified coupon code from the active order
CouponCode.Error
Component for displaying error messages. Only renders when an error is present.
Behavioral Notes:
- Returns
nullif no error is present - Displays
error.messageif no children are provided
CouponCode.Display
Component for displaying applied coupon codes with optional discount information.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
couponCode | string | Yes | - | The coupon code to display |
discount | string | No | - | Optional discount amount or description |
asChild | boolean | No | false | Use a custom component instead of the default div |
Behavioral Notes:
- Children receive
{ couponCode, discount }as render props
ActiveDiscounts.Root
The main container component that provides active discounts context. Uses a render function pattern to pass context values.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children | ChildrenProps<ActiveDiscountsContextValue> | No | - | Render function that receives the active discounts context |
ActiveDiscounts.List
Container component for the list of active discounts. Automatically returns null if there are no discounts.
Behavioral Notes:
- Returns
nullifdiscountsis empty or undefined - Children receive
{ discounts }as render props
ActiveDiscounts.Item
Container component for individual discount items. Provides discount-specific context to children.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
discount | DiscountWithPromotion | Yes | - | The discount object to display |
asChild | boolean | No | false | Use a custom component instead of the default div |
Behavioral Notes:
- Children receive
{ discount }as render props - Creates a nested context with the current discount for
RemoveButtonto access
ActiveDiscounts.RemoveButton
Button component to remove a coupon code discount. Only renders if the discount has an associated promotion with a coupon code.
Behavioral Notes:
- Returns
nullif the discount does not have apromotion.couponCode - Must be used within an
ActiveDiscounts.Itemto access the current discount - Automatically removes the coupon code associated with the discount
useCouponCode
Hook for managing coupon code application and removal. Provides state management and functions for interacting with the active order's coupon codes.
Parameters
This hook takes no parameters.
Returns
| Return Value | Type | Description |
|---|---|---|
code | string | Current coupon code input value |
isLoading | boolean | Loading state flag for API operations |
error | Error | null | Error object if coupon code application failed |
setCode | (code: string) => void | Function to update the coupon code input value |
applyCouponCode | (code: string) => Promise<void> | Function to apply a coupon code to the active order |
removeCouponCode | (code: string) => Promise<void> | Function to remove a coupon code from the active order |
useActiveDiscounts
Hook for retrieving and managing active discounts on the current order. Automatically maps order discounts with their associated promotions.
Parameters
This hook takes no parameters.
Returns
| Return Value | Type | Description |
|---|---|---|
discounts | DiscountWithPromotion[] | Array of active discounts with promotion details |
removeCouponCode | (couponCode: string) => Promise<void> | Function to remove a coupon code (delegates to useCouponCode) |
Basic Usage
Simple Coupon Code Application
- React
- React Native
import { CouponCode } from '@haus-storefront-react/discounts'
function CouponSection() {
return (
<CouponCode.Root>
<CouponCode.Input placeholder='Enter coupon code' />
<CouponCode.ApplyButton>Apply</CouponCode.ApplyButton>
<CouponCode.Error />
</CouponCode.Root>
)
}
import { CouponCode } from '@haus-storefront-react/discounts'
function CouponSection() {
return (
<CouponCode.Root>
<CouponCode.Input placeholder='Enter coupon code' />
<CouponCode.ApplyButton>Apply</CouponCode.ApplyButton>
<CouponCode.Error />
</CouponCode.Root>
)
}
Basic Hook Usage
- React
- React Native
import { useCouponCode } from '@haus-storefront-react/discounts'
function MyComponent() {
const { code, isLoading, applyCouponCode } = useCouponCode()
const handleApply = async () => {
await applyCouponCode(code)
}
if (isLoading) return <div>Applying...</div>
return <button onClick={handleApply}>Apply Coupon</button>
}
import { Pressable, Text } from 'react-native'
import { useCouponCode } from '@haus-storefront-react/discounts'
function MyComponent() {
const { code, isLoading, applyCouponCode } = useCouponCode()
const handleApply = async () => {
await applyCouponCode(code)
}
if (isLoading) return <Text>Applying...</Text>
return (
<Pressable onPress={handleApply}>
<Text>Apply Coupon</Text>
</Pressable>
)
}
Display Active Discounts
- React
- React Native
import { ActiveDiscounts } from '@haus-storefront-react/discounts'
function DiscountDisplay() {
return (
<ActiveDiscounts.Root>
<ActiveDiscounts.List>
{({ discounts }) => (
<>
{discounts.map((discount, index) => (
<ActiveDiscounts.Item key={index} discount={discount}>
{({ discount }) => <div>{discount.description}</div>}
</ActiveDiscounts.Item>
))}
</>
)}
</ActiveDiscounts.List>
</ActiveDiscounts.Root>
)
}
import { Text } from 'react-native'
import { ActiveDiscounts } from '@haus-storefront-react/discounts'
function DiscountDisplay() {
return (
<ActiveDiscounts.Root>
<ActiveDiscounts.List>
{({ discounts }) => (
<>
{discounts.map((discount, index) => (
<ActiveDiscounts.Item key={index} discount={discount}>
{({ discount }) => <Text>{discount.description}</Text>}
</ActiveDiscounts.Item>
))}
</>
)}
</ActiveDiscounts.List>
</ActiveDiscounts.Root>
)
}
Advanced Usage
Complete Checkout Discount Section
- React
- React Native
import { CouponCode, ActiveDiscounts, useCouponCode, useActiveDiscounts } from '@haus-storefront-react/discounts'
import { Price } from '@haus-storefront-react/common-ui'
function CheckoutDiscountSection() {
const { code, isLoading: couponLoading, error: couponError } = useCouponCode()
const { discounts, removeCouponCode } = useActiveDiscounts()
return (
<div className="checkout-discounts">
<CouponCode.Root>
{(couponContext) => (
<div>
<CouponCode.Input
placeholder="Enter coupon code"
disabled={couponLoading}
/>
<CouponCode.ApplyButton>
{({ isLoading }) => (
isLoading ? 'Applying...' : 'Apply Coupon'
)}
</CouponCode.ApplyButton>
<CouponCode.Error>
{(error) => error && (
<div className="error">
{error.message}
</div>
)}
</CouponCode.Error>
</div>
)}
</CouponCode.Root>
{discounts.length > 0 && (
<ActiveDiscounts.Root>
{(activeDiscountsContext) => (
<ActiveDiscounts.List>
{({ discounts }) => (
<div>
<h3>Active Discounts</h3>
{discounts.map((discount, index) => (
<ActiveDiscounts.Item key={index} discount={discount}>
{({ discount }) => (
<div className="discount-item">
<div>
<span>{discount.description}</span>
{discount.promotion?.couponCode && (
<span className="coupon-code">
Code: {discount.promotion.couponCode}
</span>
)}
</div>
<ActiveDiscounts.RemoveButton>
Remove
</ActiveDiscounts.RemoveButton>
<Price.Root
price={discount.amount}
priceWithTax={discount.amountWithTax}
currencyCode={discount.currencyCode}
>
<Price.Amount withCurrency />
</Price.Root>
</div>
))}
</div>
)}
</div>
)}
</ActiveDiscounts.List>
)}
</ActiveDiscounts.Root>
)}
</div>
)
}
import {
CouponCode,
ActiveDiscounts,
useCouponCode,
useActiveDiscounts,
} from '@haus-storefront-react/discounts'
import { Price } from '@haus-storefront-react/common-ui'
import { View, Text, TextInput, Pressable } from 'react-native'
function CheckoutDiscountSection() {
const { code, isLoading: couponLoading, error: couponError } =
useCouponCode()
const { discounts, removeCouponCode } = useActiveDiscounts()
return (
<View>
<CouponCode.Root>
{(couponContext) => (
<View>
<CouponCode.Input asChild>
<TextInput
placeholder='Enter coupon code'
editable={!couponLoading}
/>
</CouponCode.Input>
<CouponCode.ApplyButton asChild>
<Pressable disabled={couponLoading}>
<Text>{couponLoading ? 'Applying...' : 'Apply Coupon'}</Text>
</Pressable>
</CouponCode.ApplyButton>
<CouponCode.Error>
{(error) => error && <Text>{error.message}</Text>}
</CouponCode.Error>
</View>
)}
</CouponCode.Root>
{discounts.length > 0 && (
<ActiveDiscounts.Root>
{(activeDiscountsContext) => (
<ActiveDiscounts.List>
{({ discounts }) => (
<View>
<Text>Active Discounts</Text>
{discounts.map((discount, index) => (
<ActiveDiscounts.Item
key={index}
discount={discount}
>
{({ discount }) => (
<View>
<Text>{discount.description}</Text>
{discount.promotion?.couponCode && (
<Text>Code: {discount.promotion.couponCode}</Text>
)}
<ActiveDiscounts.RemoveButton asChild>
<Pressable>
<Text>Remove</Text>
</Pressable>
</ActiveDiscounts.RemoveButton>
<Price.Root
price={discount.amount}
priceWithTax={discount.amountWithTax}
currencyCode={discount.currencyCode}
>
<Price.Amount withCurrency />
</Price.Root>
</View>
)}
</ActiveDiscounts.Item>
))}
</View>
)}
</ActiveDiscounts.List>
)}
</ActiveDiscounts.Root>
)}
</View>
)
}
Conditional Rendering Patterns
- React
- React Native
import {
CouponCode,
ActiveDiscounts,
useCouponCode,
useActiveDiscounts,
} from '@haus-storefront-react/discounts'
function ConditionalDiscountUI() {
const { code, isLoading: couponLoading, error } = useCouponCode()
const { discounts, removeCouponCode } = useActiveDiscounts()
if (couponLoading) {
return <div>Loading...</div>
}
if (discounts.length === 0) {
return (
<CouponCode.Root>
{(context) => (
<div>
<CouponCode.Input placeholder='Add a coupon code' />
<CouponCode.ApplyButton>Apply</CouponCode.ApplyButton>
{error && (
<CouponCode.Error>
<div>{error.message}</div>
</CouponCode.Error>
)}
</div>
)}
</CouponCode.Root>
)
}
return (
<div>
<CouponCode.Root>
{(context) => (
<div>
<CouponCode.Input placeholder="Add another coupon" />
<CouponCode.ApplyButton>Apply</CouponCode.ApplyButton>
</div>
)}
</CouponCode.Root>
<ActiveDiscounts.Root>
{(activeDiscountsContext) => (
<ActiveDiscounts.List>
{({ discounts }) => (
<div>
<h3>Your Discounts</h3>
{discounts.map((discount, index) => (
<ActiveDiscounts.Item key={index} discount={discount}>
{({ discount }) => (
<div>
<span>{discount.description}</span>
<ActiveDiscounts.RemoveButton>
Remove
</ActiveDiscounts.RemoveButton>
</div>
)}
</ActiveDiscounts.Item>
))}
</div>
)}
</ActiveDiscounts.List>
)}
</ActiveDiscounts.Root>
</div>
)
}
import {
CouponCode,
ActiveDiscounts,
useCouponCode,
useActiveDiscounts,
} from '@haus-storefront-react/discounts'
import { View, Text, TextInput, Pressable } from 'react-native'
function ConditionalDiscountUI() {
const { code, isLoading: couponLoading, error } = useCouponCode()
const { discounts, removeCouponCode } = useActiveDiscounts()
if (couponLoading) {
return <Text>Loading...</Text>
}
if (discounts.length === 0) {
return (
<CouponCode.Root>
{(context) => (
<View>
<CouponCode.Input asChild>
<TextInput placeholder='Add a coupon code' />
</CouponCode.Input>
<CouponCode.ApplyButton asChild>
<Pressable>
<Text>Apply</Text>
</Pressable>
</CouponCode.ApplyButton>
{error && (
<CouponCode.Error>
<Text>{error.message}</Text>
</CouponCode.Error>
)}
</View>
)}
</CouponCode.Root>
)
}
return (
<View>
<CouponCode.Root>
{(context) => (
<View>
<CouponCode.Input asChild>
<TextInput placeholder='Add another coupon' />
</CouponCode.Input>
<CouponCode.ApplyButton asChild>
<Pressable>
<Text>Apply</Text>
</Pressable>
</CouponCode.ApplyButton>
</View>
)}
</CouponCode.Root>
<ActiveDiscounts.Root>
{(activeDiscountsContext) => (
<ActiveDiscounts.List>
{({ discounts }) => (
<View>
<Text>Your Discounts</Text>
{discounts.map((discount, index) => (
<ActiveDiscounts.Item key={index} discount={discount}>
{({ discount }) => (
<View>
<Text>{discount.description}</Text>
<ActiveDiscounts.RemoveButton asChild>
<Pressable>
<Text>Remove</Text>
</Pressable>
</ActiveDiscounts.RemoveButton>
</View>
)}
</ActiveDiscounts.Item>
))}
</View>
)}
</ActiveDiscounts.List>
)}
</ActiveDiscounts.Root>
</View>
)
}
Made with ❤️ by Haus Tech Team