Skip to main content

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: DataProvider that works with any SDK implementing IEcomSDK and extending BaseSDK
  • SDK base + GraphQL helper: BaseSDK, GraphQLSDK, and graphQLRequest
  • Query and mutation hooks: useQuery, useMutation, useQueryClient
  • SDK and platform access: useSdk, usePlatform, useDataProviderOptions
  • Event system: EventBus for 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 install @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

PropTypeRequiredDefaultDescription
sdkInstanceIEcomSDK & BaseSDKYes-Your SDK instance (must extend BaseSDK and implement IEcomSDK)
platform"web" | "native"NoAuto-detectedPlatform the app is running on; sets sdk.setPlatform(...)
optionsBaseOptionsNo-Configuration (see below)
updatesMaybe<BuilderQueryUpdates>No-Query builder updates (applied when SDK supports setUpdates)
customProvidersReact.ComponentType<{children}>No-Optional wrapper rendered inside the SDKContext.Provider
presetProviderPresetNo-Bundle of sdk/options/interceptors/providers merged with direct props
childrenReact.ReactNodeYes-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:

  • options are shallow-merged with preset.options. Direct options win over preset.
  • If both options/preset define interceptor strategies, options take precedence.
  • apiUrl sets 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 via options.enabledFeatures.

Plugin Configs (generic)

Pass arbitrary config objects in options.pluginConfigs. DataProvider uses duck-typing:

  • setSdk(sdk): called with your SDK
  • enabled?: boolean: defaults to true
  • getQueryUpdates(): BuilderQueryUpdates: merged (arrays concatenated) into updates
  • getRequests(): Record<string, (...args) => any>: attached as bound methods on the SDK clone
  • getEnabledFeatures(): Record<string, unknown>: applied via sdk.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 ValueTypeDescription
sdkIEcomSDK & BaseSDKThe 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

ParameterTypeRequiredDescription
optionsCustomUseQueryOptions<TQueryFnData, TError, TData>YesQuery options including queryKey and queryFn
queryClientQueryClientNoOptional 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

ParameterTypeRequiredDescription
optionsUseMutationOptions<TData, TError, TVariables, TContext>YesMutation options
queryClientQueryClientNoOptional 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

ParameterTypeRequiredDescription
eventBusEventBus<E>YesThe event bus instance (e.g., cartChannel)
eventKeyK extends keyof EYesThe event key to listen to
handlerE[K]NoOptional event handler function
identifierstringNoOptional identifier to filter events
returnLatestPayloadbooleanNoWhether 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

ParameterTypeRequiredDescription
eventBusEventBus<E>YesThe event bus instance
eventKeyK extends keyof EYesThe event key to listen to
handlerE[K]NoOptional event handler function
identifierstringNoOptional identifier to filter events
returnLatestPayloadbooleanNoWhether 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

ParameterTypeRequiredDescription
eventBusEventBus<E>YesThe event bus instance
eventKeyK extends keyof EYesThe event key
handlerE[K]YesThe handler function to remove
identifierstringNoOptional identifier

Returns

() => void - Function to remove the event listener

useEventBusEmit

Hook to emit events on an EventBus channel. Returns a callback function.

Parameters

ParameterTypeRequiredDescription
eventBusEventBus<E>YesThe event bus instance
eventKeyK extends keyof EYesThe event key to emit
identifierstringNoOptional 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

ParameterTypeRequiredDescription
eventBusEventBus<E>YesThe event bus instance
eventKeyK extends keyof EYesThe event key to retrieve history for
identifierstringNoOptional 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

ParameterTypeRequiredDescription
configEventBusConfigNoConfiguration 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

MethodSignatureDescription
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 query
  • createGraphQLMutation(json: BuilderQuery, variables?): Create a GraphQL mutation
  • createRequest<T>(json: BuilderQueryWithInheritFieldsFrom, variables, mutation?): Execute a GraphQL request
  • setUpdates(updates: BuilderQueryUpdates): Update query builder fields
  • getQueries<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:

  • inheritFieldsFrom provides the base field selection from the registered query.
  • fields are merged into the inherited fields using the same merge rules as setUpdates().
  • 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 instance
  • getQueryClient(): Access the shared QueryClient (note: this is not the React hook useQueryClient)
  • 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

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>
)
}

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

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>
)
}

Advanced Usage

Event Bus and Mutations Integration

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>
)
}

Conditional Rendering Patterns

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>
)
}

Storage and Event Bus Integration

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>
)
}

Migration Notes (from Vendure-coupled versions)

  • provider prop removed from DataProvider. Pass sdkInstance (and optional preset) instead.
  • Vendure-specific logic (SDK, queries, interceptors, storage) moved to @haus-storefront-react/providers.
  • pluginConfigs are now generic (duck-typed). You can pass mixed plugin configs via options.pluginConfigs.
  • Interceptors are configured via options.requestInterceptorStrategy and options.responseInterceptorStrategy, or via preset.
  • GraphQL helpers and HTTP client are generic in core; provider-specific behavior belongs in provider packages.

Made with ❤️ by Haus Tech Team