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
- Yarn
npm install @haus-storefront-react/quick-order
yarn add @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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onBeforeAdd | (items: AddItemToOrderInput[]) => void | Promise<void> | No | - | Called before adding items |
onSuccess | (result: UpdateMultipleOrderItemsResult) => void | No | - | Called on successful add |
onError | (error: Error) => void | No | - | Called on error |
children | ChildrenProps<QuickOrderContextValue> | No | - | React nodes or render prop |
Context Value (when using render prop)
| Property | Type | Description |
|---|---|---|
addItems | (items: AddItemToOrderInput[]) => Promise<UpdateMultipleOrderItemsResult> | Add items to the order |
isLoading | boolean | Whether an add operation is in progress |
error | Error | null | Last error that occurred |
isSuccess | boolean | Whether last operation succeeded |
data | UpdateMultipleOrderItemsResult | null | Result from last successful add |
reset | () => void | Reset 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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items | AddItemToOrderInput[] | Yes | - | Items to add |
onClick | (result: UpdateMultipleOrderItemsResult) => void | No | - | Called after adding |
asChild | boolean | No | false | Render as child element |
disabled | boolean | No | - | Disable the button |
QuickOrder.Manual.Root
Headless root for manual entry mode. Provides rows state and validation strategy.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
initialRows | number | No | 1 | Initial number of empty rows |
validationStrategy | QuickOrderVariantValidationStrategy | No | - | Resolve identifiers to variant IDs |
children | ChildrenProps<ManualContextValue> | No | - | Render prop or React nodes |
Context Value
| Property | Type | Description |
|---|---|---|
rows | QuickOrderManualRow[] | Current rows (id, identifier, quantity) |
addRow | () => void | Add a new row |
removeRow | (id: string) => void | Remove a row |
updateRow | (id, field, value) => void | Update a row field |
getValidRows | () => ParsedCsvRow[] | Rows with non-empty identifier |
clear | () => void | Reset to one empty row |
validationStrategy | QuickOrderVariantValidationStrategy | undefined | From props |
isLoading | boolean | From QuickOrder.Root |
QuickOrder.Manual.Row
Row wrapper for manual entry. Must be used within Manual.Root.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
row | QuickOrderManualRow | Yes | - | Row data |
children | ReactNode | No | - | Row subcomponents |
Subcomponents
Manual.Row.IdentifierInput– Input for identifier (e.g. SKU). Accepts standard input props; passtype,placeholder,keyboardType(React Native), etc. from your app.Manual.Row.QuantityInput– Input for quantity. Acceptstype,min,keyboardType(React Native), etc.Manual.Row.RemoveButton– Button to remove the row.
QuickOrder.Manual.AddRowButton
Button to add a new row.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
asChild | boolean | No | false | Render as child element |
disabled | boolean | No | - | Disable the button |
QuickOrder.Manual.SubmitButton
Button that adds current manual rows to the order. Uses validationStrategy from Manual.Root.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onClick | (result: UpdateMultipleOrderItemsResult | { invalidIdentifiers: string[] }) => void | No | - | Called after add or validation fail |
clearOnSuccess | boolean | No | true | Clear form when all items added |
asChild | boolean | No | false | Render as child element |
disabled | boolean | No | - | Disable the button |
QuickOrder.CSV.Root
Headless root for CSV input mode. Exposes CSV state via render props.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
validationStrategy | QuickOrderVariantValidationStrategy | No | - | Resolve identifiers to variant IDs |
children | ChildrenProps<CSVContextValue> | No | - | Render prop or React nodes |
Context Value
| Property | Type | Description |
|---|---|---|
value | string | Current CSV text |
setValue | (value: string) => void | Update CSV text (auto-parses) |
rows | ParsedCsvRow[] | Parsed rows (identifier, quantity) |
errors | CSVParseError[] | Parse errors |
hasErrors | boolean | Whether there are parse errors |
hasRows | boolean | Whether there are valid rows |
parse | () => CSVParseResult | Manually trigger parse |
clear | () => void | Clear CSV state |
validationStrategy | QuickOrderVariantValidationStrategy | undefined | From props |
isLoading | boolean | From QuickOrder.Root |
QuickOrder.CSV.SubmitButton
Button that adds current CSV rows to the order. Uses validationStrategy from CSV.Root.
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onClick | (result: UpdateMultipleOrderItemsResult | { invalidIdentifiers: string[] }) => void | No | - | Called after add or validation fail |
clearOnSuccess | boolean | No | true | Clear CSV when all items added |
asChild | boolean | No | false | Render as child element |
disabled | boolean | No | - | 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
| Parameter | Type | Required | Description |
|---|---|---|---|
options | UseQuickOrderManualOptions | No | Initial manual-row configuration |
Options Object
interface UseQuickOrderManualOptions {
initialRows?: number
}
Returns
| Return Value | Type | Description |
|---|---|---|
rows | QuickOrderManualRow[] | Current editable rows |
addRow | () => void | Appends a new empty row |
removeRow | (id: string) => void | Removes a row when more than one row remains |
updateRow | (id: string, field: 'identifier' | 'quantity', value: string | number) => void | Updates a row field |
getValidRows | () => ParsedCsvRow[] | Returns rows with non-empty identifiers |
clear | () => void | Resets 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 Value | Type | Description |
|---|---|---|
value | string | Current CSV text |
setValue | (value: string) => void | Updates the CSV text and reparses it |
rows | ParsedCsvRow[] | Parsed identifier/quantity rows |
errors | CSVParseError[] | Parse errors for invalid lines |
hasErrors | boolean | Whether parse errors are present |
hasRows | boolean | Whether valid rows were parsed |
parse | () => CSVParseResult | Manually parses the current value |
clear | () => void | Clears 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
| Parameter | Type | Required | Description |
|---|---|---|---|
csv | string | Yes | Raw CSV text |
Returns
CSVParseResult - Parsed rows and parse errors
CSVParseErrorCode
Enum-like object of parse failure codes returned by parseQuickOrderCSV.
Values
| Key | Description |
|---|---|
INVALID_FORMAT | The line could not be split into identifier and quantity |
MISSING_SECOND_COLUMN | The line did not include a quantity column |
EMPTY_FIRST_COLUMN | The identifier column was empty |
INVALID_QUANTITY | The quantity was not numeric |
QUANTITY_ZERO_OR_NEGATIVE | The quantity was less than or equal to zero |
Basic Usage
Manual Entry
- React
- React Native
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>
)
}
import { View, TextInput, Text, Pressable } from 'react-native'
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={(r) => console.log(r)} onError={(e) => console.error(e)}>
{({ isLoading }) => (
<QuickOrder.Manual.Root validationStrategy={skuStrategy} initialRows={1}>
{({ rows, getValidRows }) => (
<View>
{rows.map((row) => (
<QuickOrder.Manual.Row key={row.id} row={row}>
<View style={{ flexDirection: 'row' }}>
<QuickOrder.Manual.Row.IdentifierInput
placeholder="SKU"
keyboardType="default"
/>
<QuickOrder.Manual.Row.QuantityInput
keyboardType="number-pad"
/>
<QuickOrder.Manual.Row.RemoveButton>
<Text>Remove</Text>
</QuickOrder.Manual.Row.RemoveButton>
</View>
</QuickOrder.Manual.Row>
))}
<QuickOrder.Manual.AddRowButton>
<Text>Add row</Text>
</QuickOrder.Manual.AddRowButton>
<QuickOrder.Manual.SubmitButton>
<Text>{isLoading ? 'Adding...' : `Add ${getValidRows().length} item(s)`}</Text>
</QuickOrder.Manual.SubmitButton>
</View>
)}
</QuickOrder.Manual.Root>
)}
</QuickOrder.Root>
)
}
CSV Mode
- React
- React Native
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>
)
}
import { View, TextInput, Text, Pressable } from 'react-native'
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 }) => (
<View>
<TextInput
value={value}
onChangeText={setValue}
multiline
placeholder="SKU, quantity"
/>
{hasErrors && errors.map((err, i) => (
<Text key={i} style={{ color: 'red' }}>Line {err.line}: {err.text}</Text>
))}
{hasRows && !hasErrors && <Text>{rows.length} items ready</Text>}
<QuickOrder.CSV.SubmitButton asChild>
<Pressable disabled={hasErrors || !hasRows || isLoading}>
<Text>{isLoading ? 'Adding...' : `Add ${rows.length} items`}</Text>
</Pressable>
</QuickOrder.CSV.SubmitButton>
<Pressable onPress={clear} disabled={!value}>
<Text>Clear</Text>
</Pressable>
</View>
)}
</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
- React
- React Native
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>
))}
</>
)
}
import { Text, TextInput, View } from 'react-native'
import { useQuickOrderCSV } from '@haus-storefront-react/quick-order'
function CSVPreview() {
const { value, setValue, rows, errors, hasErrors } = useQuickOrderCSV()
return (
<View>
<TextInput value={value} onChangeText={setValue} multiline />
{!hasErrors && <Text>{rows.length} valid rows</Text>}
{errors.map((error) => (
<Text key={`${error.line}-${error.code}`}>Line {error.line}: {error.code}</Text>
))}
</View>
)
}
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