Core
Core library providing the foundational functionality for the Haus Storefront React components ecosystem.
Purpose
This library serves as the central hub for all storefront functionality, providing data providers, SDKs, query hooks, event bus, and storage abstraction.
Features
- Generic Data Provider:
DataProviderthat works with any SDK implementingIEcomSDKand extendingBaseSDK - SDK base + GraphQL helper:
BaseSDK,GraphQLSDK, andgraphQLRequest - Query and mutation hooks:
useQuery,useMutation,useQueryClient - SDK and platform access:
useSdk,usePlatform,useDataProviderOptions - Event system:
EventBusfor inter-component communication with typed channels - Storage abstraction: Platform-agnostic storage for web (localStorage) and native (AsyncStorage)
- HTTP Client utilities: Generic axios/query client and interceptor strategies
Installation
- npm
- Yarn
npm install @haus-storefront-react/core
yarn add @haus-storefront-react/core
Note: This is not a public package. Contact the Haus Tech Team for access.
API Reference
DataProvider
Main provider component for data management. Handles SDK/platform setup, feature configuration, HTTP client baseUrl/interceptors, and React Query setup (with optional persistence).
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
sdkInstance | IEcomSDK & BaseSDK | Yes | - | Your SDK instance (must extend BaseSDK and implement IEcomSDK) |
platform | "web" | "native" | No | Auto-detected | Platform the app is running on; sets sdk.setPlatform(...) |
options | BaseOptions | No | - | Configuration (see below) |
updates | Maybe<BuilderQueryUpdates> | No | - | Query builder updates (applied when SDK supports setUpdates) |
customProviders | React.ComponentType<{children}> | No | - | Optional wrapper rendered inside the SDKContext.Provider |
preset | ProviderPreset | No | - | Bundle of sdk/options/interceptors/providers merged with direct props |
children | React.ReactNode | Yes | - | Child components |
Options Object
interface PersistOptions {
enabled: boolean
storage?: Storage
maxAge?: number
}
interface BaseOptions {
apiUrl?: string // sets axios baseURL for generic client
enabledFeatures?: Features // merged with defaults and applied to SDK via BaseSDK.enableFeatures
persistOptions?: PersistOptions // when enabled, uses persisted ReactQuery provider
persistBusterResolver?: (() => Promise<string>) | string // optional persist busting
pluginConfigs?: unknown[] // duck-typed plugin configs (see Plugin Configs)
requestInterceptorStrategy?: (
config: InternalAxiosRequestConfig,
next: (config: InternalAxiosRequestConfig) => Promise<void> | void,
) => Promise<void> | void
responseInterceptorStrategy?: (
response: AxiosResponse,
next: (response: AxiosResponse) => Promise<void> | void,
) => Promise<void> | void
}
Notes:
optionsare shallow-merged withpreset.options. Directoptionswin overpreset.- If both
options/presetdefine interceptor strategies,optionstake precedence. apiUrlsets the baseURL on the generic axios instance.
Default Features
Core enables a small set of generic defaults:
hidePrice: true when the variant price is zero (supports single/range values)hideAddToCart: true when the variant price is zero You can override or extend these viaoptions.enabledFeatures.
Plugin Configs (generic)
Pass arbitrary config objects in options.pluginConfigs. DataProvider uses duck-typing:
setSdk(sdk): called with your SDKenabled?: boolean: defaults totruegetQueryUpdates(): BuilderQueryUpdates: merged (arrays concatenated) intoupdatesgetRequests(): Record<string, (...args) => any>: attached as bound methods on the SDK clonegetEnabledFeatures(): Record<string, unknown>: applied viasdk.enableFeature(key, value)- If the SDK implements
setUpdates, merged updates are applied.
Type example:
type PluginConfig = {
name?: string
enabled?: boolean
setSdk?: (sdk: IEcomSDK & BaseSDK) => void
getQueryUpdates?: () => BuilderQueryUpdates
getRequests?: () => Record<string, (...args: unknown[]) => unknown>
getEnabledFeatures?: () => Record<string, unknown>
}
useSdk
Access the configured SDK instance from the DataProvider context.
Returns
| Return Value | Type | Description |
|---|---|---|
sdk | IEcomSDK & BaseSDK | The configured SDK instance |
usePlatform
Get current platform (web or native) information from the SDK.
Returns
"web" | "native" - The current platform
useQueryClient
Access the React Query client instance. Returns the TanStack QueryClient currently in context.
useDataProviderOptions
Get data provider configuration options from context.
Returns
DataProviderProps['options'] | undefined - The data provider options, or undefined if not within a DataProvider
useQuery
Enhanced query hook with SDK integration. Wraps TanStack React Query's useQuery with the configured query client.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
options | CustomUseQueryOptions<TQueryFnData, TError, TData> | Yes | Query options including queryKey and queryFn |
queryClient | QueryClient | No | Optional query client instance (defaults to configured client) |
Returns
UseQueryResult<TData, TError> - React Query result object with data, isLoading, error, etc.
useMutation
Enhanced mutation hook with SDK integration. Wraps TanStack React Query's useMutation with the configured query client.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
options | UseMutationOptions<TData, TError, TVariables, TContext> | Yes | Mutation options |
queryClient | QueryClient | No | Optional query client instance (defaults to configured client) |
Returns
UseMutationResult<TData, TError, TVariables, TContext> - React Query mutation result object
useEventBusOn
Hook to listen to events on an EventBus channel. Returns the latest payload and a cleanup function.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
eventBus | EventBus<E> | Yes | The event bus instance (e.g., cartChannel) |
eventKey | K extends keyof E | Yes | The event key to listen to |
handler | E[K] | No | Optional event handler function |
identifier | string | No | Optional identifier to filter events |
returnLatestPayload | boolean | No | Whether to return the latest payload from history (default: true) |
Returns
[Parameters<E[K]>[0] | null, () => void] - Tuple of current payload and cleanup function
useEventBusOnce
Hook to listen to an event once on an EventBus channel.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
eventBus | EventBus<E> | Yes | The event bus instance |
eventKey | K extends keyof E | Yes | The event key to listen to |
handler | E[K] | No | Optional event handler function |
identifier | string | No | Optional identifier to filter events |
returnLatestPayload | boolean | No | Whether to return the latest payload from history (default: true) |
Returns
Parameters<E[K]>[0] | null - The event payload or null
useEventBusOff
Hook to manually remove an event listener. Normally not needed as useEventBusOn and useEventBusOnce handle cleanup automatically.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
eventBus | EventBus<E> | Yes | The event bus instance |
eventKey | K extends keyof E | Yes | The event key |
handler | E[K] | Yes | The handler function to remove |
identifier | string | No | Optional identifier |
Returns
() => void - Function to remove the event listener
useEventBusEmit
Hook to emit events on an EventBus channel. Returns a callback function.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
eventBus | EventBus<E> | Yes | The event bus instance |
eventKey | K extends keyof E | Yes | The event key to emit |
identifier | string | No | Optional identifier |
Returns
(...payload: Parameters<E[K]>) => void - Function to emit the event
useEventBusHistory
Hook to retrieve event history from an EventBus channel. Returns a promise that resolves to an array of event history items.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
eventBus | EventBus<E> | Yes | The event bus instance |
eventKey | K extends keyof E | Yes | The event key to retrieve history for |
identifier | string | No | Optional identifier to filter events |
Returns
Promise<EventHistory[]> - Promise that resolves to an array of event history items
eventbus
Factory function to create an EventBus instance with typed events.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
config | EventBusConfig | No | Configuration object with onError callback |
Returns
EventBus<E> - Event bus instance with on, off, once, emit, and getEventHistory methods
storage
Platform-agnostic storage abstraction supporting web (localStorage) and native (AsyncStorage).
Methods
| Method | Signature | Description |
|---|---|---|
getItem | (key: string) => Promise<string | null> | Get an item from storage |
setItem | (key: string, value: string) => Promise<void> | Set an item in storage |
removeItem | (key: string) => Promise<void> | Remove an item from storage |
Event Channels
Pre-configured event bus channels for common use cases.
- accountChannel: Account-related events (
'account:logged-in','account:logged-out', etc.) - cartChannel: Cart-related events (
'cart:updating','cart:updated','cart:error') - checkoutChannel: Checkout-related events (
'checkout:start','checkout:next','checkout:purchase', etc.) - orderLineChannel: Order line events (
'orderline:added','orderline:updated','orderline:removed') - productChannel: Product-related events (
'product:variant:selected','product:viewed') - productListChannel: Product list events (
'product-list:variables:changed','product-list:data:changed', etc.) - searchChannel: Search events (
'search:term','search:clear','search:enter', etc.)
GraphQLSDK
Generic GraphQL SDK for API communication. Base class that handles query building and execution.
Methods
createGraphQLQuery(json: BuilderQuery, variables?): Create a GraphQL querycreateGraphQLMutation(json: BuilderQuery, variables?): Create a GraphQL mutationcreateRequest<T>(json: BuilderQueryWithInheritFieldsFrom, variables, mutation?): Execute a GraphQL requestsetUpdates(updates: BuilderQueryUpdates): Update query builder fieldsgetQueries<T>(): Get all queries
inheritFieldsFrom
createRequest() supports inheritFieldsFrom for custom requests that should reuse the field selection from an existing registered query.
This is useful when a request returns the same GraphQL type as another query, and you want plugin-driven queryUpdates to be included automatically.
const response = await sdk.createRequest<Product[]>(
{
operation: 'relatedProducts',
inheritFieldsFrom: 'product',
variables: {
input: {
type: 'RelatedProductsInput!',
},
},
},
{
input,
},
)
You can also pass both inheritFieldsFrom and fields.
When both are present:
inheritFieldsFromprovides the base field selection from the registered query.fieldsare merged into the inherited fields using the same merge rules assetUpdates().- This means nested objects and fragment fields are merged recursively instead of replacing the inherited selection.
const response = await sdk.createRequest<Product[]>(
{
operation: 'relatedProducts',
inheritFieldsFrom: 'product',
fields: ['relatedProductIds'],
variables: {
input: {
type: 'RelatedProductsInput!',
},
},
},
{
input,
},
)
If the inherited query key is not found in the SDK query map, createRequest() falls back to the explicitly provided fields value when present and logs an error.
HTTP client utilities (generic)
Core ships a generic HTTP client and helpers. Prefer configuring via DataProvider options/preset, but functions are exported in case you need global setup:
getAxiosInstance(): Access the shared axios instancegetQueryClient(): Access the shared QueryClient (note: this is not the React hookuseQueryClient)setRequestInterceptorStrategy((config, next) => ...): Set request strategy (preferred)setResponseInterceptorStrategy((response, next) => ...): Set response strategy (preferred)HttpClient: Low-level class if you need to compose your own client
These utilities are SSR-safe; singletons are shared on the browser and isolated per request on the server.
Multiple backends: named HTTP clients
DataProvider config (apiUrl, requestInterceptorStrategy, etc.) applies to the default generic client.
If you need multiple backends (e.g. Brink + Shoplab), register named clients and use them inside your SDK:
Note: the generic client is always registered as a named client under DEFAULT_HTTP_CLIENT_NAME ("default").
So with a single backend you can just keep using getAxiosInstance() / getQueryClient().
import {
getNamedAxiosInstance,
setNamedApiBaseUrl,
setNamedRequestInterceptorStrategy,
setNamedResponseInterceptorStrategy,
} from '@haus-storefront-react/core'
setNamedApiBaseUrl('brink', 'https://api.brink.example')
setNamedApiBaseUrl('shoplab', 'https://api.shoplab.example')
setNamedRequestInterceptorStrategy('brink', async (config, next) => {
// brink headers...
await next(config)
})
setNamedResponseInterceptorStrategy('shoplab', async (response, next) => {
// shoplab token handling...
await next(response)
})
const brinkAxios = getNamedAxiosInstance('brink')
const shoplabAxios = getNamedAxiosInstance('shoplab')
Recommended: call these from your SDK constructor or from preset.onMount, and keep routing logic inside the SDK.
Basic Usage
Simple DataProvider Setup
- React
- React Native
import { DataProvider, GraphQLSDK } from '@haus-storefront-react/core'
import type { BuilderQueries } from '@haus-storefront-react/shared-types'
function App() {
// Example custom SDK (using GraphQLSDK); bring your own queries and types
const queries: BuilderQueries = {/* ... */} as never
const sdk = new GraphQLSDK(queries)
return (
<DataProvider
sdkInstance={sdk}
platform='web'
options={{
apiUrl: 'https://your-api-url',
persistOptions: {
enabled: false,
},
}}
>
<YourApp />
</DataProvider>
)
}
import { DataProvider, GraphQLSDK } from '@haus-storefront-react/core'
import type { BuilderQueries } from '@haus-storefront-react/shared-types'
function App() {
const queries: BuilderQueries = {/* ... */} as never
const sdk = new GraphQLSDK(queries)
return (
<DataProvider
sdkInstance={sdk}
platform='native'
options={{
apiUrl: 'https://your-api-url',
persistOptions: {
enabled: false,
},
}}
>
<YourApp />
</DataProvider>
)
}
Using a Provider Preset
You can encapsulate SDK, options, interceptors, and additional providers into a preset, then pass it to DataProvider. Direct options override preset.options.
import { DataProvider, GraphQLSDK, type ProviderPreset } from '@haus-storefront-react/core'
const sdk = new GraphQLSDK(/* ... */)
const preset: ProviderPreset = {
sdk,
options: {
apiUrl: 'https://your-api-url',
persistOptions: { enabled: true },
},
requestInterceptorStrategy: async (config, next) => {
// add headers/params if needed
await next(config)
},
responseInterceptorStrategy: async (response, next) => {
// handle tokens, errors, etc.
await next(response)
},
onMount: ({ sdk, options, setBaseUrl }) => {
// one-time initialization
},
}
export function App() {
return (
<DataProvider sdkInstance={sdk} preset={preset}>
<YourApp />
</DataProvider>
)
}
Basic Hook Usage
- React
- React Native
import { useSdk, useQuery } from '@haus-storefront-react/core'
function ProductList() {
const sdk = useSdk()
const { data, isLoading } = useQuery({
queryKey: ['products'],
queryFn: () => sdk.search({ take: 10 }),
})
if (isLoading) return <div>Loading...</div>
if (!data) return <div>No products</div>
return (
<div>
{data.items.map((product) => (
<div key={product.id}>{product.name}</div>
))}
</div>
)
}
import { View, Text } from 'react-native'
import { useSdk, useQuery } from '@haus-storefront-react/core'
function ProductList() {
const sdk = useSdk()
const { data, isLoading } = useQuery({
queryKey: ['products'],
queryFn: () => sdk.search({ take: 10 }),
})
if (isLoading) return <Text>Loading...</Text>
if (!data) return <Text>No products</Text>
return (
<View>
{data.items.map((product) => (
<Text key={product.id}>{product.name}</Text>
))}
</View>
)
}
Advanced Usage
Event Bus and Mutations Integration
- React
- React Native
import {
DataProvider,
useSdk,
useQuery,
useMutation,
useEventBusOn,
useEventBusEmit,
cartChannel,
CartAction,
} from '@haus-storefront-react/core'
function AdvancedApp() {
return (
<DataProvider
sdkInstance={sdk}
options={{
apiUrl: 'https://your-api-url',
enabledFeatures: {
customPriceCurrency: 'SEK',
},
persistOptions: {
enabled: true,
},
}}
>
<ShoppingCart />
</DataProvider>
)
}
function ShoppingCart() {
const sdk = useSdk()
const [cartPayload, unsubscribe] = useEventBusOn(
cartChannel,
'cart:updated',
({ data }) => {
console.log('Cart updated:', data)
},
)
const emit = useEventBusEmit(cartChannel, 'cart:updating')
const { data: order } = useQuery({
queryKey: ['activeOrder'],
queryFn: () => sdk.activeOrder(),
})
const addItemMutation = useMutation({
mutationFn: (input) => sdk.addItemToOrder(input),
onSuccess: () => {
emit({
data: {},
action: CartAction.ADD,
})
},
})
return (
<div>
{order?.lines.map((line) => (
<div key={line.id}>{line.productVariant.name}</div>
))}
<button
onClick={() =>
addItemMutation.mutate({
productVariantId: '123',
quantity: 1,
})
}
>
Add Item
</button>
</div>
)
}
import { View, Text, Button } from 'react-native'
import {
DataProvider,
useSdk,
useQuery,
useMutation,
useEventBusOn,
useEventBusEmit,
cartChannel,
CartAction,
} from '@haus-storefront-react/core'
function AdvancedApp() {
return (
<DataProvider
sdkInstance={sdk}
options={{
apiUrl: 'https://your-api-url',
enabledFeatures: {
customPriceCurrency: 'SEK',
},
persistOptions: {
enabled: true,
},
}}
>
<ShoppingCart />
</DataProvider>
)
}
function ShoppingCart() {
const sdk = useSdk()
const [cartPayload, unsubscribe] = useEventBusOn(
cartChannel,
'cart:updated',
({ data }) => {
console.log('Cart updated:', data)
},
)
const emit = useEventBusEmit(cartChannel, 'cart:updating')
const { data: order } = useQuery({
queryKey: ['activeOrder'],
queryFn: () => sdk.activeOrder(),
})
const addItemMutation = useMutation({
mutationFn: (input) => sdk.addItemToOrder(input),
onSuccess: () => {
emit({
data: {},
action: CartAction.ADD,
})
},
})
return (
<View>
{order?.lines.map((line) => (
<Text key={line.id}>{line.productVariant.name}</Text>
))}
<Button
title='Add Item'
onPress={() =>
addItemMutation.mutate({
productVariantId: '123',
quantity: 1,
})
}
/>
</View>
)
}
Conditional Rendering Patterns
- React
- React Native
import { useSdk, useQuery, usePlatform } from '@haus-storefront-react/core'
function ConditionalComponent() {
const sdk = useSdk()
const platform = usePlatform()
const { data: order, isLoading } = useQuery({
queryKey: ['activeOrder'],
queryFn: () => sdk.activeOrder(),
})
if (isLoading) {
return <div>Loading order...</div>
}
if (!order) {
return <div>No active order</div>
}
if (platform === 'native') {
return (
<div>
<h1>Mobile Cart</h1>
{order.lines.map((line) => (
<div key={line.id}>{line.productVariant.name}</div>
))}
</div>
)
}
return (
<div>
<h1>Web Cart</h1>
<table>
<thead>
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{order.lines.map((line) => (
<tr key={line.id}>
<td>{line.productVariant.name}</td>
<td>{line.quantity}</td>
<td>{line.linePriceWithTax}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
import { View, Text } from 'react-native'
import { useSdk, useQuery, usePlatform } from '@haus-storefront-react/core'
function ConditionalComponent() {
const sdk = useSdk()
const platform = usePlatform()
const { data: order, isLoading } = useQuery({
queryKey: ['activeOrder'],
queryFn: () => sdk.activeOrder(),
})
if (isLoading) {
return <Text>Loading order...</Text>
}
if (!order) {
return <Text>No active order</Text>
}
if (platform === 'native') {
return (
<View>
<Text>Mobile Cart</Text>
{order.lines.map((line) => (
<Text key={line.id}>{line.productVariant.name}</Text>
))}
</View>
)
}
return (
<View>
<Text>Web Cart</Text>
{order.lines.map((line) => (
<Text key={line.id}>
{line.productVariant.name} • {line.quantity} • {line.linePriceWithTax}
</Text>
))}
</View>
)
}
Storage and Event Bus Integration
- React
- React Native
import {
storage,
useEventBusOn,
accountChannel,
} from '@haus-storefront-react/core'
function AccountComponent() {
const [loggedInPayload] = useEventBusOn(
accountChannel,
'account:logged-in',
async ({ username }) => {
await storage.setItem('username', username)
console.log('User logged in:', username)
},
)
const handleLogout = async () => {
await storage.removeItem('username')
const username = await storage.getItem('username')
console.log('Username cleared:', username) // null
}
return (
<div>
{loggedInPayload && <div>Welcome, {loggedInPayload.username}</div>}
<button onClick={handleLogout}>Logout</button>
</div>
)
}
import { View, Text, Button } from 'react-native'
import {
storage,
useEventBusOn,
accountChannel,
} from '@haus-storefront-react/core'
function AccountComponent() {
const [loggedInPayload] = useEventBusOn(
accountChannel,
'account:logged-in',
async ({ username }) => {
await storage.setItem('username', username)
console.log('User logged in:', username)
},
)
const handleLogout = async () => {
await storage.removeItem('username')
const username = await storage.getItem('username')
console.log('Username cleared:', username) // null
}
return (
<View>
{loggedInPayload && <Text>Welcome, {loggedInPayload.username}</Text>}
<Button title='Logout' onPress={handleLogout} />
</View>
)
}
Migration Notes (from Vendure-coupled versions)
providerprop removed fromDataProvider. PasssdkInstance(and optionalpreset) instead.- Vendure-specific logic (SDK, queries, interceptors, storage) moved to
@haus-storefront-react/providers. pluginConfigsare now generic (duck-typed). You can pass mixed plugin configs viaoptions.pluginConfigs.- Interceptors are configured via
options.requestInterceptorStrategyandoptions.responseInterceptorStrategy, or viapreset. - GraphQL helpers and HTTP client are generic in core; provider-specific behavior belongs in provider packages.
Made with ❤️ by Haus Tech Team