Skip to content

Commit c0ebcee

Browse files
committed
feat(core): CHECKOUT-9513 Allow client to hydrate initial state
1 parent 0e409a0 commit c0ebcee

22 files changed

+214
-30
lines changed

packages/core/src/billing/billing-address-reducer.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { Action, combineReducers, composeReducers } from '@bigcommerce/data-store';
22

3-
import { CheckoutAction, CheckoutActionType } from '../checkout';
3+
import {
4+
CheckoutAction,
5+
CheckoutActionType,
6+
CheckoutHydrateAction,
7+
CheckoutHydrateActionType,
8+
} from '../checkout';
49
import { clearErrorReducer } from '../common/error';
510
import { objectSet, replace } from '../common/utility';
611
import { OrderAction } from '../order';
@@ -29,14 +34,17 @@ export default function billingAddressReducer(
2934

3035
function dataReducer(
3136
data: BillingAddress | undefined,
32-
action: CheckoutAction | BillingAddressAction | OrderAction,
37+
action: CheckoutAction | BillingAddressAction | OrderAction | CheckoutHydrateAction,
3338
): BillingAddress | undefined {
3439
switch (action.type) {
3540
case BillingAddressActionType.UpdateBillingAddressSucceeded:
3641
case BillingAddressActionType.ContinueAsGuestSucceeded:
3742
case CheckoutActionType.LoadCheckoutSucceeded:
3843
return replace(data, action.payload && action.payload.billingAddress);
3944

45+
case CheckoutHydrateActionType.HydrateInitialState:
46+
return replace(data, action.payload?.checkout.billingAddress);
47+
4048
default:
4149
return data;
4250
}

packages/core/src/cart/cart-reducer.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { Action, combineReducers, composeReducers } from '@bigcommerce/data-store';
22

33
import { BillingAddressAction, BillingAddressActionType } from '../billing';
4-
import { CheckoutAction, CheckoutActionType } from '../checkout';
4+
import {
5+
CheckoutAction,
6+
CheckoutActionType,
7+
CheckoutHydrateAction,
8+
CheckoutHydrateActionType,
9+
} from '../checkout';
510
import { clearErrorReducer } from '../common/error';
611
import { objectMerge, objectSet } from '../common/utility';
712
import {
@@ -32,7 +37,8 @@ function dataReducer(
3237
| CheckoutAction
3338
| ConsignmentAction
3439
| CouponAction
35-
| GiftCertificateAction,
40+
| GiftCertificateAction
41+
| CheckoutHydrateAction,
3642
): Cart | undefined {
3743
switch (action.type) {
3844
case BillingAddressActionType.UpdateBillingAddressSucceeded:
@@ -48,6 +54,9 @@ function dataReducer(
4854
case GiftCertificateActionType.RemoveGiftCertificateSucceeded:
4955
return objectMerge(data, action.payload && action.payload.cart);
5056

57+
case CheckoutHydrateActionType.HydrateInitialState:
58+
return objectMerge(data, action.payload?.checkout.cart);
59+
5160
default:
5261
return data;
5362
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Action } from '@bigcommerce/data-store';
2+
3+
import CheckoutInitialState from './checkout-initial-state';
4+
5+
export enum CheckoutHydrateActionType {
6+
HydrateInitialState = 'HYDRATE_INITIAL_STATE',
7+
}
8+
9+
export interface CheckoutHydrateAction extends Action<CheckoutInitialState> {
10+
type: CheckoutHydrateActionType.HydrateInitialState;
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Config } from '../config';
2+
import { Extension } from '../extension';
3+
import { FormFields } from '../form';
4+
5+
import Checkout from './checkout';
6+
7+
export default interface CheckoutInitialState {
8+
config: Config;
9+
formFields: FormFields;
10+
checkout: Checkout;
11+
extensions: Extension[];
12+
}

packages/core/src/checkout/checkout-reducer.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import CheckoutState, {
2222
CheckoutStatusesState,
2323
DEFAULT_STATE,
2424
} from './checkout-state';
25+
import { CheckoutHydrateAction, CheckoutHydrateActionType } from './checkout-hydrate-actions';
2526

2627
export default function checkoutReducer(
2728
state: CheckoutState = DEFAULT_STATE,
@@ -46,7 +47,8 @@ function dataReducer(
4647
| GiftCertificateAction
4748
| OrderAction
4849
| SpamProtectionAction
49-
| StoreCreditAction,
50+
| StoreCreditAction
51+
| CheckoutHydrateAction,
5052
): CheckoutDataState | undefined {
5153
switch (action.type) {
5254
case CheckoutActionType.LoadCheckoutSucceeded:
@@ -78,6 +80,19 @@ function dataReducer(
7880
case OrderActionType.SubmitOrderSucceeded:
7981
return objectSet(data, 'orderId', action.payload && action.payload.order.orderId);
8082

83+
case CheckoutHydrateActionType.HydrateInitialState:
84+
return objectMerge(
85+
data,
86+
omit(action.payload?.checkout, [
87+
'billingAddress',
88+
'cart',
89+
'consignments',
90+
'customer',
91+
'coupons',
92+
'giftCertificates',
93+
]),
94+
) as CheckoutDataState;
95+
8196
default:
8297
return data;
8398
}

packages/core/src/checkout/create-checkout-service.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { createRequestSender } from '@bigcommerce/request-sender';
22

33
import { getDefaultLogger, Logger } from '../common/log';
4+
import { getConfig } from '../config/configs.mock';
5+
import { getExtensions } from '../extension/extension.mock';
6+
import { getFormFields } from '../form/form.mock';
47

8+
import CheckoutInitialState from './checkout-initial-state';
59
import CheckoutService from './checkout-service';
10+
import { getCheckout } from './checkouts.mock';
611
import createCheckoutService from './create-checkout-service';
712

813
jest.mock('@bigcommerce/request-sender');
@@ -43,4 +48,23 @@ describe('createCheckoutService()', () => {
4348

4449
expect(logger.warn).toHaveBeenCalled();
4550
});
51+
52+
it('creates instance with initial data', () => {
53+
const initialState: CheckoutInitialState = {
54+
config: getConfig(),
55+
formFields: getFormFields(),
56+
checkout: getCheckout(),
57+
extensions: getExtensions(),
58+
};
59+
const checkoutService = createCheckoutService({ initialState });
60+
const state = checkoutService.getState();
61+
62+
expect(checkoutService).toBeInstanceOf(CheckoutService);
63+
expect(state.data.getCheckout()).toEqual(initialState.checkout);
64+
expect(state.data.getConfig()).toEqual(initialState.config.storeConfig);
65+
expect(state.data.getCustomerAccountFields()).toEqual(
66+
initialState.formFields.customerAccount,
67+
);
68+
expect(state.data.getExtensions()).toEqual(initialState.extensions);
69+
});
4670
});

packages/core/src/checkout/create-checkout-service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import { StoreCreditActionCreator, StoreCreditRequestSender } from '../store-cre
6161
import { SubscriptionsActionCreator, SubscriptionsRequestSender } from '../subscription';
6262

6363
import CheckoutActionCreator from './checkout-action-creator';
64+
import CheckoutInitialState from './checkout-initial-state';
6465
import CheckoutRequestSender from './checkout-request-sender';
6566
import CheckoutService from './checkout-service';
6667
import CheckoutValidator from './checkout-validator';
@@ -109,7 +110,7 @@ export default function createCheckoutService(options?: CheckoutServiceOptions):
109110
};
110111
const { locale = '', shouldWarnMutation = true } = options || {};
111112
const requestSender = createRequestSender({ host: options && options.host });
112-
const store = createCheckoutStore({ config }, { shouldWarnMutation });
113+
const store = createCheckoutStore({ config }, options?.initialState, { shouldWarnMutation });
113114
const paymentClient = createPaymentClient(store);
114115
const orderRequestSender = new OrderRequestSender(requestSender);
115116
const checkoutRequestSender = new CheckoutRequestSender(requestSender);
@@ -213,4 +214,5 @@ export interface CheckoutServiceOptions {
213214
host?: string;
214215
shouldWarnMutation?: boolean;
215216
externalSource?: string;
217+
initialState?: CheckoutInitialState;
216218
}

packages/core/src/checkout/create-checkout-store.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,35 @@ import { createDataStore } from '@bigcommerce/data-store';
22

33
import { createRequestErrorFactory } from '../common/error';
44

5+
import { CheckoutHydrateActionType } from './checkout-hydrate-actions';
6+
import CheckoutInitialState from './checkout-initial-state';
57
import CheckoutStore, { CheckoutStoreOptions } from './checkout-store';
68
import CheckoutStoreState from './checkout-store-state';
79
import createActionTransformer from './create-action-transformer';
810
import createCheckoutStoreReducer from './create-checkout-store-reducer';
911
import { createInternalCheckoutSelectorsFactory } from './create-internal-checkout-selectors';
1012

1113
export default function createCheckoutStore(
12-
initialState: Partial<CheckoutStoreState> = {},
14+
initialStoreState: Partial<CheckoutStoreState> = {},
15+
initialServerState?: CheckoutInitialState,
1316
options?: CheckoutStoreOptions,
1417
): CheckoutStore {
1518
const actionTransformer = createActionTransformer(createRequestErrorFactory());
1619
const createInternalCheckoutSelectors = createInternalCheckoutSelectorsFactory();
1720
const stateTransformer = (state: CheckoutStoreState) => createInternalCheckoutSelectors(state);
21+
const reducer = createCheckoutStoreReducer();
22+
const hydrateAction = {
23+
type: CheckoutHydrateActionType.HydrateInitialState,
24+
payload: initialServerState,
25+
};
1826

19-
return createDataStore(createCheckoutStoreReducer(), initialState, {
20-
actionTransformer,
21-
stateTransformer,
22-
...options,
23-
});
27+
return createDataStore(
28+
reducer,
29+
reducer(initialStoreState as CheckoutStoreState, hydrateAction),
30+
{
31+
actionTransformer,
32+
stateTransformer,
33+
...options,
34+
},
35+
);
2436
}

packages/core/src/checkout/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './checkout-actions';
2+
export * from './checkout-hydrate-actions';
23

34
export { default as Checkout, CheckoutPayment } from './checkout';
45
export { default as CHECKOUT_DEFAULT_INCLUDES } from './checkout-default-includes';

packages/core/src/config/config-reducer.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Action, combineReducers, composeReducers } from '@bigcommerce/data-store';
22

3+
import { CheckoutHydrateAction, CheckoutHydrateActionType } from '../checkout';
34
import { clearErrorReducer } from '../common/error';
45
import { objectMerge, objectSet } from '../common/utility';
56

@@ -20,11 +21,17 @@ export default function configReducer(
2021
return reducer(state, action);
2122
}
2223

23-
function dataReducer(data: Config | undefined, action: LoadConfigAction): Config | undefined {
24+
function dataReducer(
25+
data: Config | undefined,
26+
action: LoadConfigAction | CheckoutHydrateAction,
27+
): Config | undefined {
2428
switch (action.type) {
2529
case ConfigActionType.LoadConfigSucceeded:
2630
return objectMerge(data, action.payload);
2731

32+
case CheckoutHydrateActionType.HydrateInitialState:
33+
return objectMerge(data, action.payload?.config);
34+
2835
default:
2936
return data;
3037
}

0 commit comments

Comments
 (0)