Getting Started
Welcome to Haus Tech's headless storefront ecosystem! This guide will help you get up and running with our components, libraries, and tools.
Overview
Haus Storefront Components is a collection of headless, cross-platform React components and hooks designed for building e-commerce storefronts. The library is organized into several categories:
- Core Libraries: Foundation functionality including SDKs, providers, and data management
- Common Libraries: Shared utilities, hooks, and UI components
- Store Components: E-commerce specific components (cart, product list, checkout, etc.)
- Plugin Configurations: Platform integrations and extensions
Prerequisites
Before you begin, make sure you have:
- Node.js version 18 or higher
- React 19 or higher
- TypeScript (recommended for type safety)
- Access to Haus Tech's private npm registry (contact the Haus Tech Team for access)
- A Vendure backend API endpoint (Vendure is our default provider - see Vendure Integration for details)
Quick Start
1. Install the Core Library
The core library (@haus-storefront-react/core) is the foundation of the ecosystem and provides the DataProvider, SDKs, and essential hooks.
- npm
- Yarn
npm install @haus-storefront-react/core
yarn add @haus-storefront-react/core
Note: The package is not publicly available. Contact the Haus Tech Team for access to the private npm registry.
2. Set Up the DataProvider
Wrap your application with the DataProvider component. This provides the SDK, query client, and platform detection to all child components.
- React
- React Native
import { DataProvider } from "@haus-storefront-react/core";
function App() {
return (
<DataProvider
provider='vendure'
platform='web'
options={{
apiUrl: "https://your-vendure-api.com/shop-api",
vendureToken: "YOUR_CHANNEL_TOKEN",
persistOptions: {
enabled: false,
},
}}
>
<YourApplication />
</DataProvider>
);
}
import { DataProvider } from "@haus-storefront-react/core";
function App() {
return (
<DataProvider
provider='vendure'
platform='native'
options={{
apiUrl: "https://your-vendure-api.com/shop-api",
vendureToken: "YOUR_CHANNEL_TOKEN",
persistOptions: {
enabled: false,
},
}}
>
<YourApplication />
</DataProvider>
);
}
3. Use Core Hooks
Once the DataProvider is set up, you can use hooks throughout your application:
- React
- React Native
import { useSdk, useQuery } from "@haus-storefront-react/core";
function ProductList() {
const sdk = useSdk();
const { data, isLoading, error } = useQuery({
queryKey: ["products"],
queryFn: () => sdk.search({ take: 10 }),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>No products found</div>;
return (
<div>
{data.items.map((product) => (
<div key={product.id}>
<h3>{product.name}</h3>
<p>{product.description}</p>
</div>
))}
</div>
);
}
import { useSdk, useQuery } from "@haus-storefront-react/core";
import { FlatList, Text, View } from "react-native";
function ProductList() {
const sdk = useSdk();
const { data, isLoading, error } = useQuery({
queryKey: ["products"],
queryFn: () => sdk.search({ take: 10 }),
});
if (isLoading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
if (error) {
return (
<View>
<Text>Error: {error.message}</Text>
</View>
);
}
if (!data) {
return (
<View>
<Text>No products found</Text>
</View>
);
}
return (
<FlatList
data={data.items}
keyExtractor={(product) => product.id}
renderItem={({ item: product }) => (
<View>
<Text>{product.name}</Text>
<Text>{product.description}</Text>
</View>
)}
/>
);
}
Basic Examples
Example 1: Shopping Cart
- React
- React Native
import { DataProvider } from "@haus-storefront-react/core";
import { Cart } from "@haus-storefront-react/cart";
import { AddItemToOrder } from "@haus-storefront-react/add-item-to-order";
function ShoppingApp() {
return (
<DataProvider
provider='vendure'
options={{
apiUrl: 'https://your-api.com/shop-api',
vendureToken: 'YOUR_TOKEN',
}}
>
<ProductPage />
<CartDisplay />
</DataProvider>
); }
function ProductPage() {
return (
<div>
<h1>Product Name</h1>
<AddItemToOrder.Root productVariantId='123'>
<AddItemToOrder.QuantityInput />
<AddItemToOrder.AddButton>Add to Cart</AddItemToOrder.AddButton>
</AddItemToOrder.Root>
</div>
); }
function CartDisplay() {
return (
<Cart.Root>
{({ data: order, isLoading }) => (
<>
{isLoading && <div>Loading cart...</div>}
{order && (
<>
<Cart.Items />
<Cart.Totals />
<Cart.CheckoutButton>Checkout</Cart.CheckoutButton>
</>
)}
<Cart.Empty>
<p>Your cart is empty</p>
</Cart.Empty>
</>
)}
</Cart.Root>
); }
import { DataProvider } from "@haus-storefront-react/core";
import { Cart } from "@haus-storefront-react/cart";
import { AddItemToOrder } from "@haus-storefront-react/add-item-to-order";
import {
Pressable,
SafeAreaView,
ScrollView,
Text,
View,
} from "react-native";
function ShoppingApp() {
return (
<DataProvider
provider='vendure'
platform='native'
options={{
apiUrl: "https://your-api.com/shop-api",
vendureToken: "YOUR_TOKEN",
}}
>
<SafeAreaView>
<ScrollView>
<ProductPage />
<CartDisplay />
</ScrollView>
</SafeAreaView>
</DataProvider>
);
}
function ProductPage() {
return (
<View>
<Text>Product Name</Text>
<AddItemToOrder.Root productVariantId='123'>
<AddItemToOrder.QuantityInput />
<AddItemToOrder.AddButton asChild>
<Pressable>
<Text>Add to Cart</Text>
</Pressable>
</AddItemToOrder.AddButton>
</AddItemToOrder.Root>
</View>
);
}
function CartDisplay() {
return (
<Cart.Root>
{({ data: order, isLoading }) => (
<View>
{isLoading && <Text>Loading cart...</Text>}
{order && (
<>
<Cart.Items />
<Cart.Totals />
<Cart.CheckoutButton asChild>
<Pressable>
<Text>Checkout</Text>
</Pressable>
</Cart.CheckoutButton>
</>
)}
<Cart.Empty>
<Text>Your cart is empty</Text>
</Cart.Empty>
</View>
)}
</Cart.Root>
);
}
Example 2: Product List with Search
- React
- React Native
import { DataProvider } from "@haus-storefront-react/core";
import { ProductList } from "@haus-storefront-react/product-list";
import { Search } from "@haus-storefront-react/search";
function Storefront() {
return (
<DataProvider
provider='vendure'
options={{
apiUrl: "https://your-api.com/shop-api",
vendureToken: "YOUR_TOKEN",
}} >
<Search.Root>
<Search.Input placeholder='Search products...' />
<Search.Results>
<Search.Products />
</Search.Results>
</Search.Root>
<ProductList.Root>
<ProductList.Items>
{({ items }) => (
<div>
{items.map((item) => (
<ProductList.Item key={item.id} product={item}>
<ProductList.Image />
<ProductList.Name />
<ProductList.Price />
<ProductList.AddToCartButton />
</ProductList.Item>
))}
</div>
)}
</ProductList.Items>
<ProductList.Pagination />
</ProductList.Root>
</DataProvider>
);
}
import { DataProvider } from "@haus-storefront-react/core";
import { ProductList } from "@haus-storefront-react/product-list";
import { Search } from "@haus-storefront-react/search";
import { ScrollView, Text, TextInput, View } from "react-native";
function Storefront() {
return (
<DataProvider
provider='vendure'
platform='native'
options={{
apiUrl: "https://your-api.com/shop-api",
vendureToken: "YOUR_TOKEN",
}}
>
<ScrollView>
<Search.Root>
<Search.Input asChild>
<TextInput placeholder='Search products...' />
</Search.Input>
<Search.Results>
<Search.Products />
</Search.Results>
</Search.Root>
<ProductList.Root>
<ProductList.Items>
{({ items }) => (
<View>
{items.map((item) => (
<ProductList.Item key={item.id} product={item}>
<View>
<ProductList.Image />
<ProductList.Name />
<ProductList.Price />
<ProductList.AddToCartButton />
</View>
</ProductList.Item>
))}
</View>
)}
</ProductList.Items>
<ProductList.Pagination />
</ProductList.Root>
</ScrollView>
</DataProvider>
);
}
Example 3: Using Hooks Directly
- React
- React Native
import {
DataProvider,
useSdk,
useQuery,
useMutation,
} from "@haus-storefront-react/core";
import { useActiveOrder } from "@haus-storefront-react/hooks";
function CustomCart() {
const sdk = useSdk();
const { data: order, isLoading } = useActiveOrder();
const addItemMutation = useMutation({
mutationFn: (input) => sdk.addItemToOrder(input),
onSuccess: () => {
console.log("Item added to cart!");
},
});
const handleAddToCart = () => {
addItemMutation.mutate({
productVariantId: "123",
quantity: 1,
});
};
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h2>Cart ({order?.lines.length || 0} items)</h2>
{order?.lines.map((line) => (
<div key={line.id}>
{line.productVariant.name} - {line.quantity} x {line.linePriceWithTax}
</div>
))}
<button onClick={handleAddToCart}>Add Item</button>
</div>
); }
import {
DataProvider,
useSdk,
useMutation,
} from "@haus-storefront-react/core";
import { useActiveOrder } from "@haus-storefront-react/hooks";
import { Button, Text, View } from "react-native";
function CustomCart() {
const sdk = useSdk();
const { data: order, isLoading } = useActiveOrder();
const addItemMutation = useMutation({
mutationFn: (input) => sdk.addItemToOrder(input),
onSuccess: () => {
console.log("Item added to cart!");
},
});
const handleAddToCart = () => {
addItemMutation.mutate({
productVariantId: "123",
quantity: 1,
});
};
if (isLoading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
return (
<View>
<Text>Cart ({order?.lines.length || 0} items)</Text>
{order?.lines.map((line) => (
<Text key={line.id}>
{line.productVariant.name} - {line.quantity} x {line.linePriceWithTax}
</Text>
))}
<Button title='Add Item' onPress={handleAddToCart} />
</View>
);
}
Configuration Options
DataProvider Options
The DataProvider accepts various configuration options:
- React
- React Native
<DataProvider
provider='vendure'
platform='web' // or "native" - auto-detected if not specified
options={{
// Required
apiUrl: "https://your-api.com/shop-api",
// Optional
vendureToken: "YOUR_CHANNEL_TOKEN",
locale: "en",
currency: "USD",
autoSetLocaleFromDocument: true,
// Feature flags
enabledFeatures: {
customPriceCurrency: "SEK",
},
// State persistence
persistOptions: {
enabled: true,
maxAge: 3600000, // 1 hour in milliseconds
},
// Plugin configurations
pluginConfigs: [
// Your plugin configs here
],
// Custom interceptors
requestInterceptorStrategy: (config, defaultHandler) => {
// Custom request logic
return defaultHandler(config);
},
responseInterceptorStrategy: (response, defaultHandler) => {
// Custom response logic
return defaultHandler(response);
},
}}
>
<App />
</DataProvider>
import { SafeAreaView } from "react-native";
<DataProvider
provider='vendure'
platform='native'
options={{
apiUrl: "https://your-api.com/shop-api",
vendureToken: "YOUR_CHANNEL_TOKEN",
locale: "en-US",
currency: "USD",
enabledFeatures: {
customPriceCurrency: "SEK",
},
persistOptions: {
enabled: true,
storageKey: "haus-cart",
maxAge: 3600000,
},
pluginConfigs: [
// Your plugin configs here
],
requestInterceptorStrategy: (config, defaultHandler) => {
// Custom request logic
return defaultHandler(config);
},
responseInterceptorStrategy: (response, defaultHandler) => {
// Custom response logic
return defaultHandler(response);
},
}}
>
<SafeAreaView>
<App />
</SafeAreaView>
</DataProvider>
Key Concepts
Headless Components
All components are "headless" - they provide logic and state management but allow complete UI customization. You control the styling and layout.
Compound Components
Many components use the compound component pattern for flexibility:
- React
- React Native
<Cart.Root>
<Cart.Items>
<Cart.Item orderLine={line}>
<Cart.Item.Image />
<Cart.Item.Price />
</Cart.Item>
</Cart.Items>
<Cart.Summary>{/* Summary content */}</Cart.Summary>
</Cart.Root>
import { Image, Text, View } from "react-native";
<Cart.Root>
<Cart.Items>
<Cart.Item orderLine={line}>
<Cart.Item.Image asChild>
<Image
source={{ uri: line.productVariant.featuredAsset?.preview }}
/>
</Cart.Item.Image>
<Cart.Item.Price />
</Cart.Item>
</Cart.Items>
<Cart.Summary>
<View>
<Text>Summary content</Text>
</View>
</Cart.Summary>
</Cart.Root>
asChild – Use Your Own Elements in Compound Components
Many components in the Haus Storefront libraries have an asChild prop. This allows you to replace the underlying HTML element (such as a <button>, <input>, or <form>) with your own component or element, without losing access to all the logic, props, and context.
This is particularly useful if you want to use your own styling, a UI library, or for example, a Next.js Link instead of a regular <a> tag.
Example: Replace Button with Your Own Component
- React
- React Native
import { Login } from "@haus-storefront-react/authentication";
<Login.Root>
<Login.Form asChild>
<form className='my-form'>
<Login.EmailInput asChild>
<input className='my-input' />
</Login.EmailInput>
<Login.PasswordInput asChild>
<input className='my-input' />
</Login.PasswordInput>
<Login.SubmitButton asChild>
<button className='my-button'>Log in</button>
</Login.SubmitButton>
</form>
</Login.Form>
</Login.Root>;
import { Login } from "@haus-storefront-react/authentication";
import { Pressable, Text, TextInput, View } from "react-native";
<Login.Root>
<Login.Form asChild>
<View>
<Login.EmailInput asChild>
<TextInput placeholder='Email' />
</Login.EmailInput>
<Login.PasswordInput asChild>
<TextInput placeholder='Password' secureTextEntry />
</Login.PasswordInput>
<Login.SubmitButton asChild>
<Pressable>
<Text>Log in</Text>
</Pressable>
</Login.SubmitButton>
</View>
</Login.Form>
</Login.Root>;
In the example above:
- All props and event handling from the Login components are passed through to your own elements.
- You can use any element (e.g.,
<button>or a custom React component) and still get the correct logic and accessibility.
Why Use asChild?
- Full control over markup and styling – You decide exactly which element is rendered.
- Keep all logic – You still get the correct props, events, and accessibility attributes.
- Integrate with UI libraries – Works with Chakra UI, Material UI, Tailwind, or any other UI library.
asChildis built on a "slot" pattern and works in all compound components where it's available.
Render Props
Components often use render props to provide context:
- React
- React Native
<ProductList.Root>
{({ products, isLoading, error }) => {
// Access context data
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading products</div>;
return <div>{/* Your UI */}</div>;
}}
</ProductList.Root>
import { Text, View } from "react-native";
<ProductList.Root>
{({ products, isLoading, error }) => {
if (isLoading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
if (error) {
return (
<View>
<Text>Error loading products</Text>
</View>
);
}
return <View>{/* Your UI */}</View>;
}}
</ProductList.Root>
Platform Detection
Components work on both web (React) and mobile (React Native) platforms. The library automatically detects the platform and uses appropriate primitives. You can explicitly set the platform if needed:
- Web: Runs in browsers, uses
localStoragefor persistence - Native: Runs in React Native, uses
AsyncStoragefor persistence
- React
- React Native
<DataProvider
provider='vendure'
platform='web' // Explicitly set platform
options={{ apiUrl: "..." }}
>
<App />
</DataProvider>
import { SafeAreaView } from "react-native";
<DataProvider
provider='vendure'
platform='native' // Explicitly set platform
options={{ apiUrl: "..." }}
>
<SafeAreaView>
<App />
</SafeAreaView>
</DataProvider>
Next Steps
Now that you have the basics set up, explore the documentation:
- Learn about the Core Library: Understand the DataProvider, SDKs, and hooks
- Explore Storefront Components: Build your storefront with our pre-built components
- Use Storefront Hooks: Access e-commerce functionality with React hooks
- Customize Components: Use the ComponentProvider to customize component rendering
- Integrate with Vendure: Learn about plugin configurations and type-safe integrations
- Level up with Advanced Guides: Dive into
Advanced Playbookfor component composition, SDK extensions, and plugin strategies
Happy building!
Made with ❤️ by Haus Tech Team