Skip to content

Commit

Permalink
Merge branch 'develop' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
bfeister committed Oct 12, 2022
2 parents be4a650 + 9e0f596 commit 7ccc586
Show file tree
Hide file tree
Showing 30 changed files with 519 additions and 151 deletions.
43 changes: 43 additions & 0 deletions packages/commerce-sdk-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,49 @@ A library of React hooks for fetching data from Salesforce B2C Commerce.

The full documentation for PWA Kit and Managed Runtime is hosted on the [Salesforce Developers](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/overview) portal.

## PWA-Kit Integration

> To integration this library with your PWA-Kit application you can use the `CommerceApiProvider` directly given that you use the `withReactQuery` higher order component to wrap your `AppConfig` component. Below is a snippet of how this is accomplished.
```
// app/components/_app-config/index.jsx
import {withReactQuery} from 'pwa-kit-react-sdk/ssr/universal/components/with-react-query'
const AppConfig = ({children}) => {
return (
<CommerceApiProvider {...commerceApiProviderProps}>
{children}
</CommerceApiProvider>
)
}
export default withReactQuery(AppConfig)
```

## Generic Integration

> You can use this library in any React application provided you bring your own QueryClient and QueryClientProvider. Below is a sample integration:
```
import {QueryClient, QueryClientConfig, QueryClientProvider} from '@tanstack/react-query'
const App = ({children}) => {
const queryClient = new QueryClient(queryClientConfig)
return (
<QueryClientProvider client={queryClient}>
<CommerceApiProvider {...commerceApiProviderProps}>
{children}
</CommerceApiProvider>
</QueryClientProvider>
)
}
export default App
```

### Useful Links:

- [Get Started](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/getting-started.html)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ const CategoryComponent = ({id}: {id: string}): ReactElement => {
</div>
)
}

const IncorrectArguments = (): ReactElement => {
// @ts-ignore
const {isLoading, error} = useProducts({FOO: ''})

return (
<div>
{isLoading && <span>Loading...</span>}
{error && <span>error</span>}
</div>
)
}

const tests = [
{
hook: 'useProducts',
Expand Down Expand Up @@ -117,6 +130,15 @@ const tests = [
expect(screen.getByText('error')).toBeInTheDocument()
expect(screen.queryByText('Loading...')).toBeNull()
})
},
{
name: 'returns validation error',
assertions: async () => {
renderWithProviders(<IncorrectArguments />)
await waitFor(() => screen.getByText('error'))
expect(screen.getByText('error')).toBeInTheDocument()
expect(screen.queryByText('Loading...')).toBeNull()
}
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ function useProducts(
arg: UseProductsArg,
options?: UseQueryOptions<DataType<Client['getProducts']> | Response, Error>
) {
if (!arg.ids) {
throw new Error('ids is required for useProducts')
}
const {headers, rawResponse, ...parameters} = arg
return useAsync(
['products', arg],
Expand Down Expand Up @@ -67,9 +64,6 @@ function useProduct(
arg: UseProductArg,
options?: UseQueryOptions<DataType<Client['getProduct']> | Response, Error>
): UseQueryResult<DataType<Client['getProduct']> | Response, Error> {
if (!arg.id) {
throw new Error('id is required for useProduct.')
}
const {headers, rawResponse, ...parameters} = arg
return useAsync(
['product', arg],
Expand Down Expand Up @@ -105,9 +99,6 @@ function useCategories(
arg: UseCategoriesArg,
options?: UseQueryOptions<DataType<Client['getCategories']> | Response, Error>
): UseQueryResult<DataType<Client['getCategories']> | Response, Error> {
if (!arg.ids) {
throw new Error('ids is required for useCategories')
}
const {headers, rawResponse, ...parameters} = arg
return useAsync(
['categories', arg],
Expand Down
32 changes: 32 additions & 0 deletions packages/commerce-sdk-react/src/provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,35 @@ test(
expect(screen.getByText(TEST_CONFIG.organizationId)).toBeInTheDocument()
})
)

test(
'api clients optional config are passed properly',
withMocks(async () => {
const Component = () => {
const api = useCommerceApi()
return (
<ul>
<li>{api?.shopperSearch?.clientConfig?.headers?.['correlation-id']}</li>
<li>{api?.shopperSearch?.clientConfig?.fetchOptions?.timeout}</li>
</ul>
)
}
const commerceApiProviderConfig = {
headers: {'correlation-id': '373a3f80-6bbb-4157-a617-63d27fb15769'},
fetchOptions: {
timeout: 50
}
}
renderWithProviders(
<Component />,
{},
{
commerceApiProvider: commerceApiProviderConfig
}
)
expect(
screen.getByText(commerceApiProviderConfig.headers['correlation-id'])
).toBeInTheDocument()
expect(screen.getByText(commerceApiProviderConfig.fetchOptions.timeout)).toBeInTheDocument()
})
)
11 changes: 3 additions & 8 deletions packages/commerce-sdk-react/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React, {ReactElement, useEffect, useMemo} from 'react'
import React, {Fragment, ReactElement, useEffect, useMemo} from 'react'
import {
ShopperBaskets,
ShopperContexts,
Expand All @@ -20,7 +20,6 @@ import {
} from 'commerce-sdk-isomorphic'
import Auth from './auth'
import {ApiClientConfigParams, ApiClients} from './hooks/types'
import {QueryClient, QueryClientConfig, QueryClientProvider} from '@tanstack/react-query'
import {ReactQueryDevtools} from '@tanstack/react-query-devtools'

export interface CommerceApiProviderProps extends ApiClientConfigParams {
Expand All @@ -29,7 +28,6 @@ export interface CommerceApiProviderProps extends ApiClientConfigParams {
locale: string
currency: string
redirectURI: string
queryClientConfig?: QueryClientConfig
fetchOptions?: ShopperBasketsTypes.FetchOptions
headers?: Record<string, string>
}
Expand Down Expand Up @@ -58,7 +56,6 @@ const CommerceApiProvider = (props: CommerceApiProviderProps): ReactElement => {
organizationId,
proxy,
redirectURI,
queryClientConfig,
fetchOptions,
siteId,
shortCode
Expand Down Expand Up @@ -108,18 +105,16 @@ const CommerceApiProvider = (props: CommerceApiProviderProps): ReactElement => {
auth.ready()
}, [auth])

const queryClient = new QueryClient(queryClientConfig)

// TODO: wrap the children with:
// - context for enabling useServerEffect hook
// - context for sharing the auth object that would manage the tokens -> this will probably be for internal use only
return (
<QueryClientProvider client={queryClient}>
<Fragment>
<CommerceApiContext.Provider value={apiClients}>
<AuthContext.Provider value={auth}>{children}</AuthContext.Provider>
</CommerceApiContext.Provider>
<ReactQueryDevtools />
</QueryClientProvider>
</Fragment>
)
}

Expand Down
42 changes: 28 additions & 14 deletions packages/commerce-sdk-react/src/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {render, RenderOptions} from '@testing-library/react'
import jwt from 'jsonwebtoken'
import nock from 'nock'
import React from 'react'
import CommerceApiProvider from './provider'
import CommerceApiProvider, {CommerceApiProviderProps} from './provider'
import {QueryClient, QueryClientProvider, QueryClientProviderProps} from '@tanstack/react-query'

export const TEST_CONFIG = {
proxy: 'http://localhost:3000/mobify/proxy/api',
Expand All @@ -21,30 +22,43 @@ export const TEST_CONFIG = {
locale: 'en_US',
currency: 'USD'
}
const TestProviders = (props: {children: React.ReactNode}) => {
const TestProviders = (props: {
children: React.ReactNode
commerceApiProvider?: Partial<CommerceApiProviderProps>
}) => {
const {commerceApiProvider} = props
const queryClient = new QueryClient({
// During testing, we want things to fail immediately
defaultOptions: {queries: {retry: false}, mutations: {retry: false}}
})
return (
<CommerceApiProvider
{...TEST_CONFIG}
queryClientConfig={{
defaultOptions: {queries: {retry: false}, mutations: {retry: false}}
}}
>
{props.children}
</CommerceApiProvider>
<QueryClientProvider client={queryClient}>
<CommerceApiProvider {...TEST_CONFIG} {...commerceApiProvider}>
{props.children}
</CommerceApiProvider>
</QueryClientProvider>
)
}

/**
* Render your component, which will be wrapped with all the necessary Provider components
*
* @param component
* @param children
* @param options - additional options for testing-library's render function
* @param providerProps - additional props to pass to providers in TestProvider component
*/
export const renderWithProviders = (
component: React.ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
children: React.ReactElement,
options?: Omit<RenderOptions, 'wrapper'>,
providerProps?: {
commerceApiProvider?: Partial<CommerceApiProviderProps>
}
): void => {
render(component, {wrapper: TestProviders, ...options})
render(children, {
// eslint-disable-next-line react/display-name
wrapper: () => <TestProviders {...providerProps}>{children}</TestProviders>,
...options
})
}

type NockBackOptions = {
Expand Down
1 change: 1 addition & 0 deletions packages/pwa-kit-react-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## v2.3.0-dev (Aug 25, 2022)
- Support `react-query` server-side data fetching. [#724](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/724)
- Add server-safe default configuration for `queryClientConfig` option.. [#734](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/734)
## v2.2.0 (Aug 25, 2022)
## v2.1.0 (Jul 05, 2022)
- Remove console logs from route component. [#651](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/651)
Expand Down
32 changes: 17 additions & 15 deletions packages/pwa-kit-react-sdk/src/ssr/browser/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter as Router} from 'react-router-dom'
import {CorrelationIdProvider} from '../universal/contexts'
import DeviceContext from '../universal/device-context'
import {ServerContext, CorrelationIdProvider} from '../universal/contexts'
import App from '../universal/components/_app'
import {getAppConfig} from '../universal/compatibility'
import Switch from '../universal/components/switch'
Expand Down Expand Up @@ -75,20 +75,22 @@ export const start = () => {
.then(() => new Promise((resolve) => loadableReady(resolve)))
.then(() => {
ReactDOM.hydrate(
<Router>
<CorrelationIdProvider correlationId={() => uuidv4()}>
<DeviceContext.Provider value={{type: window.__DEVICE_TYPE__}}>
<AppConfig locals={locals}>
<Switch
error={error}
appState={window.__PRELOADED_STATE__}
routes={routes}
App={WrappedApp}
/>
</AppConfig>
</DeviceContext.Provider>
</CorrelationIdProvider>
</Router>,
<ServerContext.Provider value={{}}>
<Router>
<CorrelationIdProvider correlationId={() => uuidv4()}>
<DeviceContext.Provider value={{type: window.__DEVICE_TYPE__}}>
<AppConfig locals={locals}>
<Switch
error={error}
appState={window.__PRELOADED_STATE__}
routes={routes}
App={WrappedApp}
/>
</AppConfig>
</DeviceContext.Provider>
</CorrelationIdProvider>
</Router>
</ServerContext.Provider>,
rootEl,
() => {
window.__HYDRATING__ = false
Expand Down
38 changes: 27 additions & 11 deletions packages/pwa-kit-react-sdk/src/ssr/server/react-rendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import serialize from 'serialize-javascript'

import {getAssetUrl} from '../universal/utils'
import DeviceContext from '../universal/device-context'
import {CorrelationIdProvider} from '../universal/contexts'
import {ServerContext, CorrelationIdProvider} from '../universal/contexts'

import Document from '../universal/components/_document'
import App from '../universal/components/_app'
Expand Down Expand Up @@ -192,22 +192,38 @@ export const render = async (req, res, next) => {
}
}

const OuterApp = ({res, error, App, appState, routes, routerContext, location, deviceType}) => {
const OuterApp = ({
req,
res,
error,
App,
appState,
routes,
routerContext,
location,
deviceType
}) => {
const AppConfig = getAppConfig()
return (
<Router location={location} context={routerContext}>
<CorrelationIdProvider correlationId={res.locals.requestId} resetOnPageChange={false}>
<DeviceContext.Provider value={{type: deviceType}}>
<AppConfig locals={res.locals}>
<Switch error={error} appState={appState} routes={routes} App={App} />
</AppConfig>
</DeviceContext.Provider>
</CorrelationIdProvider>
</Router>
<ServerContext.Provider value={{req, res}}>
<Router location={location} context={routerContext}>
<CorrelationIdProvider
correlationId={res.locals.requestId}
resetOnPageChange={false}
>
<DeviceContext.Provider value={{type: deviceType}}>
<AppConfig locals={res.locals}>
<Switch error={error} appState={appState} routes={routes} App={App} />
</AppConfig>
</DeviceContext.Provider>
</CorrelationIdProvider>
</Router>
</ServerContext.Provider>
)
}

OuterApp.propTypes = {
req: PropTypes.object,
res: PropTypes.object,
error: PropTypes.object,
App: PropTypes.elementType,
Expand Down
Loading

0 comments on commit 7ccc586

Please sign in to comment.