Skip to main content

Quick Order

Headless component for quickly adding multiple products to an order. Supports manual entry (row-by-row) and CSV paste modes with identifier-to-variant resolution via a configurable strategy.

Purpose

This library provides a flexible system for bulk add-to-cart flows. Users can enter SKUs and quantities manually or paste a CSV. A validation strategy (e.g. from the Vendure plugin) resolves identifiers to variant IDs before adding to the order. Manual and CSV modes have separate roots and contexts, so only the active mode's data is submitted.

Features

  • Manual entry mode with add/remove rows, identifier + quantity inputs
  • CSV paste mode with auto-parse and error reporting
  • Configurable validation strategy per mode (e.g. SKU → variant ID)
  • Separate contexts for Manual and CSV – switching modes does not mix data
  • Cross-platform support (web and React Native via asChild pattern)
  • Headless: no styling, full UI control
  • Utility exports for CSV parsing and parse error handling
  • Standalone hooks for manual-row and CSV state management

Installation

npm install @haus-storefront-react/quick-order

Note: This is not a public package. Contact the Haus Tech Team for access.

API Reference

Components

QuickOrder.Root

Root provider component. Wraps all Quick Order functionality. Must wrap Manual.Root and CSV.Root.

Props

PropTypeRequiredDefaultDescription
onBeforeAdd(items: AddItemToOrderInput[]) => void | Promise<void>No-Called before adding items
onSuccess(result: UpdateMultipleOrderItemsResult) => voidNo-Called on successful add
onError(error: Error) => voidNo-Called on error
childrenChildrenProps<QuickOrderContextValue>No-React nodes or render prop

Context Value (when using render prop)

PropertyTypeDescription
addItems(items: AddItemToOrderInput[]) => Promise<UpdateMultipleOrderItemsResult>Add items to the order
isLoadingbooleanWhether an add operation is in progress
errorError | nullLast error that occurred
isSuccessbooleanWhether last operation succeeded
dataUpdateMultipleOrderItemsResult | nullResult from last successful add
reset() => voidReset state

QuickOrder.Button

Button that adds items to the order when clicked. Use when you have pre-built AddItemToOrderInput[] (e.g. from your own form).

Props

PropTypeRequiredDefaultDescription
itemsAddItemToOrderInput[]Yes-Items to add
onClick(result: UpdateMultipleOrderItemsResult) => voidNo-Called after adding
asChildbooleanNofalseRender as child element
disabledbooleanNo-Disable the button

QuickOrder.Manual.Root

Headless root for manual entry mode. Provides rows state and validation strategy.

Props

PropTypeRequiredDefaultDescription
initialRowsnumberNo1Initial number of empty rows
validationStrategyQuickOrderVariantValidationStrategyNo-Resolve identifiers to variant IDs
childrenChildrenProps<ManualContextValue>No-Render prop or React nodes

Context Value

PropertyTypeDescription
rowsQuickOrderManualRow[]Current rows (id, identifier, quantity)
addRow() => voidAdd a new row
removeRow(id: string) => voidRemove a row
updateRow(id, field, value) => voidUpdate a row field
getValidRows() => ParsedCsvRow[]Rows with non-empty identifier
clear() => voidReset to one empty row
validationStrategyQuickOrderVariantValidationStrategy | undefinedFrom props
isLoadingbooleanFrom QuickOrder.Root

QuickOrder.Manual.Row

Row wrapper for manual entry. Must be used within Manual.Root.

Props

PropTypeRequiredDefaultDescription
rowQuickOrderManualRowYes-Row data
childrenReactNodeNo-Row subcomponents

Subcomponents

  • Manual.Row.IdentifierInput – Input for identifier (e.g. SKU). Accepts standard input props; pass type, placeholder, keyboardType (React Native), etc. from your app.
  • Manual.Row.QuantityInput – Input for quantity. Accepts type, min, keyboardType (React Native), etc.
  • Manual.Row.RemoveButton – Button to remove the row.

QuickOrder.Manual.AddRowButton

Button to add a new row.

Props

PropTypeRequiredDefaultDescription
asChildbooleanNofalseRender as child element
disabledbooleanNo-Disable the button

QuickOrder.Manual.SubmitButton

Button that adds current manual rows to the order. Uses validationStrategy from Manual.Root.

Props

PropTypeRequiredDefaultDescription
onClick(result: UpdateMultipleOrderItemsResult | { invalidIdentifiers: string[] }) => voidNo-Called after add or validation fail
clearOnSuccessbooleanNotrueClear form when all items added
asChildbooleanNofalseRender as child element
disabledbooleanNo-Disable the button

QuickOrder.CSV.Root

Headless root for CSV input mode. Exposes CSV state via render props.

Props

PropTypeRequiredDefaultDescription
validationStrategyQuickOrderVariantValidationStrategyNo-Resolve identifiers to variant IDs
childrenChildrenProps<CSVContextValue>No-Render prop or React nodes

Context Value

PropertyTypeDescription
valuestringCurrent CSV text
setValue(value: string) => voidUpdate CSV text (auto-parses)
rowsParsedCsvRow[]Parsed rows (identifier, quantity)
errorsCSVParseError[]Parse errors
hasErrorsbooleanWhether there are parse errors
hasRowsbooleanWhether there are valid rows
parse() => CSVParseResultManually trigger parse
clear() => voidClear CSV state
validationStrategyQuickOrderVariantValidationStrategy | undefinedFrom props
isLoadingbooleanFrom QuickOrder.Root

QuickOrder.CSV.SubmitButton

Button that adds current CSV rows to the order. Uses validationStrategy from CSV.Root.

Props

PropTypeRequiredDefaultDescription
onClick(result: UpdateMultipleOrderItemsResult | { invalidIdentifiers: string[] }) => voidNo-Called after add or validation fail
clearOnSuccessbooleanNotrueClear CSV when all items added
asChildbooleanNofalseRender as child element
disabledbooleanNo-Disable the button

Hooks

useQuickOrderManual

Hook for managing manual rows. It is used internally by QuickOrder.Manual.Root, but can also power custom UIs outside the component API.

Parameters

ParameterTypeRequiredDescription
optionsUseQuickOrderManualOptionsNoInitial manual-row configuration

Options Object

interface UseQuickOrderManualOptions {
initialRows?: number
}

Returns

Return ValueTypeDescription
rowsQuickOrderManualRow[]Current editable rows
addRow() => voidAppends a new empty row
removeRow(id: string) => voidRemoves a row when more than one row remains
updateRow(id: string, field: 'identifier' | 'quantity', value: string | number) => voidUpdates a row field
getValidRows() => ParsedCsvRow[]Returns rows with non-empty identifiers
clear() => voidResets the form to one empty row

useQuickOrderCSV

Hook for managing CSV quick-order text, parsed rows, and parse errors. It auto-parses whenever setValue() is called.

Returns

Return ValueTypeDescription
valuestringCurrent CSV text
setValue(value: string) => voidUpdates the CSV text and reparses it
rowsParsedCsvRow[]Parsed identifier/quantity rows
errorsCSVParseError[]Parse errors for invalid lines
hasErrorsbooleanWhether parse errors are present
hasRowsbooleanWhether valid rows were parsed
parse() => CSVParseResultManually parses the current value
clear() => voidClears all CSV state

Utilities

parseQuickOrderCSV

Parses raw CSV-like text into identifier + quantity rows without applying storefront-specific semantics. Use a validation strategy afterwards to resolve identifiers to variant IDs.

Parameters

ParameterTypeRequiredDescription
csvstringYesRaw CSV text

Returns

CSVParseResult - Parsed rows and parse errors

CSVParseErrorCode

Enum-like object of parse failure codes returned by parseQuickOrderCSV.

Values

KeyDescription
INVALID_FORMATThe line could not be split into identifier and quantity
MISSING_SECOND_COLUMNThe line did not include a quantity column
EMPTY_FIRST_COLUMNThe identifier column was empty
INVALID_QUANTITYThe quantity was not numeric
QUANTITY_ZERO_OR_NEGATIVEThe quantity was less than or equal to zero

Basic Usage

Manual Entry

import { QuickOrder } from '@haus-storefront-react/quick-order'
import { quickOrderVariantSkuValidationStrategy } from '@haus-storefront-react/vendure-plugin-configs/products-by-sku'

const skuStrategy = quickOrderVariantSkuValidationStrategy()

function QuickOrderForm() {
return (
<QuickOrder.Root
onSuccess={(result) => console.log('Added:', result)}
onError={(err) => console.error(err)}
>
{({ isLoading }) => (
<QuickOrder.Manual.Root validationStrategy={skuStrategy} initialRows={1}>
{({ rows, getValidRows, clear }) => (
<>
{rows.map((row) => (
<QuickOrder.Manual.Row key={row.id} row={row}>
<div>
<QuickOrder.Manual.Row.IdentifierInput
type="text"
placeholder="SKU or Variant ID"
/>
<QuickOrder.Manual.Row.QuantityInput type="number" min={1} />
<QuickOrder.Manual.Row.RemoveButton>Remove</QuickOrder.Manual.Row.RemoveButton>
</div>
</QuickOrder.Manual.Row>
))}
<QuickOrder.Manual.AddRowButton>Add row</QuickOrder.Manual.AddRowButton>
<QuickOrder.Manual.SubmitButton>
{isLoading ? 'Adding...' : `Add ${getValidRows().length} item(s)`}
</QuickOrder.Manual.SubmitButton>
</>
)}
</QuickOrder.Manual.Root>
)}
</QuickOrder.Root>
)
}

CSV Mode

import { QuickOrder } from '@haus-storefront-react/quick-order'
import { quickOrderVariantSkuValidationStrategy } from '@haus-storefront-react/vendure-plugin-configs/products-by-sku'

const skuStrategy = quickOrderVariantSkuValidationStrategy()

function QuickOrderCSV() {
return (
<QuickOrder.Root onSuccess={(r) => console.log(r)} onError={(e) => console.error(e)}>
{({ isLoading }) => (
<QuickOrder.CSV.Root validationStrategy={skuStrategy}>
{({ value, setValue, rows, errors, hasErrors, hasRows, clear }) => (
<>
<textarea
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="SKU, quantity"
/>
{hasErrors && (
<ul>
{errors.map((err, i) => (
<li key={i}>Line {err.line}: {err.text}</li>
))}
</ul>
)}
{hasRows && !hasErrors && <p>{rows.length} items ready</p>}
<QuickOrder.CSV.SubmitButton>
{isLoading ? 'Adding...' : `Add ${rows.length} items`}
</QuickOrder.CSV.SubmitButton>
<button onClick={clear}>Clear</button>
</>
)}
</QuickOrder.CSV.Root>
)}
</QuickOrder.Root>
)
}

Simple Button (pre-built items)

<QuickOrder.Root onSuccess={(r) => console.log(r)}>
<QuickOrder.Button
items={[
{ productVariantId: 'variant-1', quantity: 2 },
{ productVariantId: 'variant-2', quantity: 1 },
]}
>
Add to Cart
</QuickOrder.Button>
</QuickOrder.Root>

CSV Format

Supported formats:

# Comments start with #
SKU, quantity # Comma
SKU; quantity # Semicolon
SKU quantity # Tab
SKU 10 # Space (quantity numeric)

Example:

T1503-100G, 1
A7000-25G, 10
W4502-1L, 2

Advanced Usage

Using the hooks without the headless components

import { useQuickOrderCSV, parseQuickOrderCSV } from '@haus-storefront-react/quick-order'

function CSVPreview() {
const { value, setValue, rows, errors, hasErrors } = useQuickOrderCSV()

return (
<>
<textarea value={value} onChange={(e) => setValue(e.target.value)} />
{!hasErrors && <p>{rows.length} valid rows</p>}
{errors.map((error) => (
<p key={`${error.line}-${error.code}`}>Line {error.line}: {error.code}</p>
))}
</>
)
}

Handling validation failures explicitly

When a validation strategy reports unresolved identifiers, QuickOrder.Root triggers onError, and the submit button onClick callback also receives invalidIdentifiers.

import { QuickOrder } from '@haus-storefront-react/quick-order'

function QuickOrderWithErrors({ strategy }: { strategy: any }) {
return (
<QuickOrder.Root
onError={(error) => {
console.error(error)
}}
>
<QuickOrder.CSV.Root validationStrategy={strategy}>
{({ rows }) => (
<QuickOrder.CSV.SubmitButton
onClick={(result) => {
if ('invalidIdentifiers' in result && result.invalidIdentifiers?.length) {
console.log('Invalid identifiers:', result.invalidIdentifiers)
}
}}
>
Add {rows.length} items
</QuickOrder.CSV.SubmitButton>
)}
</QuickOrder.CSV.Root>
</QuickOrder.Root>
)
}

Types

interface AddItemToOrderInput {
productVariantId: string
quantity: number
}

interface ParsedCsvRow {
identifier: string
quantity: number
}

// From @haus-storefront-react/strategies
interface QuickOrderVariantValidationStrategy {
validate: (items: { identifier: string; quantity: number }[]) => Promise<{
items: AddItemToOrderInput[]
invalidIdentifiers?: string[]
errorResults?: UpdateOrderItemErrorResult[]
}>
}

const CSVParseErrorCode = {
INVALID_FORMAT: 'INVALID_FORMAT',
MISSING_SECOND_COLUMN: 'MISSING_SECOND_COLUMN',
EMPTY_FIRST_COLUMN: 'EMPTY_FIRST_COLUMN',
INVALID_QUANTITY: 'INVALID_QUANTITY',
QUANTITY_ZERO_OR_NEGATIVE: 'QUANTITY_ZERO_OR_NEGATIVE',
}

interface CSVParseError {
line: number
text: string
code: CSVParseErrorCode
value?: string
}

Made with ❤️ by Haus Tech Team