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 initialRows empty rows
validationStrategyQuickOrderVariantValidationStrategy | undefinedFrom props
isLoadingbooleanFrom QuickOrder.Root
rowResultsManualRowSubmitResult[]Latest per-row submit results
errorsUpdateOrderItemErrorResult[]Flattened list of current row errors
hasErrorsbooleanConvenience flag for generic error UI
successCountnumberNumber of rows added in last submit
hasSuccessbooleanConvenience flag for success UI
setSubmitState(state: ManualSubmitState) => voidInternal submit-state setter

QuickOrder.Manual.Row

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

Props

PropTypeRequiredDefaultDescription
rowQuickOrderManualRowYes-Row data
childrenChildrenProps<ManualRowContextValue>No-Row subcomponents or render prop

Subcomponents

  • Manual.Row.IdentifierInput – Input for identifier (e.g. SKU). Accepts standard input props plus onValueChange and onValueBlur callbacks for row-aware value handling.
  • Manual.Row.QuantityInput – Input for quantity. Accepts type, min, keyboardType (React Native), etc.
  • Manual.Row.RemoveButton – Button to remove the row.

When children is a render prop, Manual.Row also exposes error for that row:

<QuickOrder.Manual.Row row={row}>
{({ row, error }) => (
<>
<QuickOrder.Manual.Row.IdentifierInput />
{(error?.submitError || error?.validationError) && (
<p>{error?.submitError?.message ?? error?.validationError?.message}</p>
)}
</>
)}
</QuickOrder.Manual.Row>

IdentifierInput extra props

PropTypeRequiredDefaultDescription
onValueChange(value: string, rowId: string) => voidNo-Called on each input change with current value and row ID
onValueBlur(value: string, rowId: string) => voidNo-Called on blur with the blurred input value and row ID

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: ManualSubmitResult) => voidNo-Called after add/validation flow
clearOnSuccessbooleanNotrueClear form when all items added
asChildbooleanNofalseRender as child element
disabledbooleanNo-Disable the button

ManualSubmitResult contains:

  • order?: Order (latest returned order)
  • errorResults: UpdateOrderItemErrorResult[] (combined validation + submit errors)
  • invalidIdentifiers?: string[]
  • rowResults: ManualRowSubmitResult[] (per-row success/error outcome)

Note: If your package uses semver, this onClick signature change is a breaking API change and should be released in a major version.

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

useQuickOrderSkuAutocomplete

Hook for fetching SKU autocomplete suggestions for manual Quick Order input using the active SDK from DataProvider.

Parameters

ParameterTypeRequiredDefaultDescription
querystringYes-Search input used to match SKU suggestions (trimmed before cache/query)
takenumberNo8Maximum number of suggestions requested from search

Returns

UseQueryResult<QuickOrderSkuAutocompleteResult, Error> - React Query result with:

  • data.items: normalized SKU suggestion items
  • data.totalItems: count of returned SKU-filtered, deduplicated suggestions
  • standard React Query state (isLoading, isFetching, error, etc.)

Utilities

searchQuickOrderSkuSuggestionsFromSdk

Searches and normalizes SKU suggestions for manual Quick Order input from a provided SDK instance. This utility is dependency-free and can be reused by wrappers or non-React consumers.

Exported from this package as searchQuickOrderSkuSuggestions.

Parameters

ParameterTypeRequiredDefaultDescription
sdkunknownYes-SDK object. Must expose searchField(input) to return search suggestions
querystringYes-Search input used to filter SKU values (trimmed/lowercased before matching)
takenumberNo8Maximum number of suggestions requested from search

Returns

Promise<QuickOrderSkuAutocompleteResult> - Normalized suggestion payload:

  • items: deduplicated suggestions by productVariantId
  • totalItems: count of items after SKU filtering and deduplication

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