2 min read

Customise the Wishlist Button to select a Variant and update the Product Details screen

Last Updated - Platform 22.0 - SDK 17.0

This guide will walk through how you can customise the WishlistWidget to present the Variant Selector for Variant level wishlists. This is a nice customisation for the PDP to improve the user wishlisting experience as seen below.

BeforeAfter
WishlistVariantSelector Before WishlistVariantSelector After

Selecting a Variant

  1. Create a new file for your custom WishlistWidget somewhere like Sources/WishlistButton/Views.
  2. Subclass the WishlistWidget and override the makeInteractionMiddleware function.
import PoqCatalogue
import PoqCatalogueClient
class CustomWishlistWidget: WishlistWidget {
override func makeInteractionMiddleware() {
let middleware = super.makeInteractionMiddleware()
return Middleware { store, action in
store.continue(action)
// TODO: Add custom code here.
middleware?.execute(store: store, action: action)
}
}
}
  1. Handle the WishlistWidgetAction.wishlist action by presenting the variant selector, only if the input.id is not a variant.

We can use the VariantSelectorCoordinator from Containers to conveniently present the Variant Selector. When the user completes variant selection we can add the selected variant to the Wishlist.

return Middleware { store, action in
store.continue(action)
switch action {
case WishlistWidgetAction.wishlist:
// Ensure the widget is not loading and there is an input.
guard let state = store.state, !state.itemState.isLoading, var input = state.input else { fallthrough }
// Only handle invalid input by showing the variant selector to select the variant to wishlist.
guard !Settings.Wishlist.level.isValid(id: input.id) else { fallthrough }
Container.shared.coordinators.variantSelector().presentVariantSelector(
state: .init(id: .init(id: input.id)),
completion: { variant in
input.id.listingId = variant.listingId
input.id.variantId = variant.id
// Add the variant to the wishlist (add only).
store.dispatch(ServiceAction.WishlistEntry.update(.init(id: input.id, isWishlisted: true)))
}
)
default:
middleware?.execute(store: store, action: action)
}
}
  1. Lastly, inject your custom widget in your AppModule in the didAddToPlatform or setUpWishlist function.
Container.shared.views.wishlistWidget.replace { CustomWishlistWidget() }

Nice! Now the wishlist button selects a variant and adds it to the Wishlist.

But there are a couple of issues with this implementation on the PDP:

  • When the variant selector opens it loads the Product with a call to the API.
  • When the user selects a variant the PDP is not updated to match the form selection.
  • For products with many forms the user needs to select forms that may have been selected on the PDP.

Let's solve these issues.

Passing a better state to the Widget

For this we are going to pass the entire ProductDetailState (or any ProductState) to the widget. Doing this allows the widget to setup matching the current PDP selection.

  1. Create a new file for a custom ProductDetailsViewDataMapper somewhere like Sources/ProductDetails/Mappers.
  2. Subclass the PoqProductDetailsViewDataMapper and override the mapWishlist function to pass the entire state as custom data.
import PoqCatalogue
import PoqProductDetails
class CustomProductDetailsViewDataMapper: PoqProductDetailsViewDataMapper {
override func mapWishlist(from state: ProductDetailsState) -> WishlistWidgetState.Input? {
var wishlist = super.mapWishlist(from: state)
wishlist?.customData = VariantSelectorState(state)
return wishlist
}
}
  1. Inject your custom mapper in your AppModule in the didAddToPlatform or setUpProductDetails function.
Container.shared.mappers.productDetailsViewDataMapper.replace { CustomProductDetailsViewDataMapper() }
  1. Update your CustomWishlistWidget code to use the custom data state.
// Use the custom state if one was passed, otherwise default to using the id.
let inputState = input.customData(VariantSelectorState.self)
Container.shared.coordinators.variantSelector().presentVariantSelector(
state: inputState ?? .init(id: .init(id: input.id)),
completion: { variant in
...
}
)

Great! Now the variant selector doesn't make an API call and skips user selected PDP forms.

Let's tackle the last issue by updating the PDP as forms are selected.

Update the PDP as forms are selected

If you've followed all the previous steps you'll notice that after wishlisting a variant the PDP stays the same. This feels odd for the user so let's make the PDP select the variant.

  1. Open your CustomWishlistWidget and add a delegate to delegate formSelected to the owning feature.
class CustomWishlistWidget: WishlistWidget {
weak var formSelectionDelegate: FormSelectorDelegate?
}
  1. Update the Variant Selector presentation code to use the formSelection parameter.
Container.shared.coordinators.variantSelector().presentVariantSelector(
state: inputState ?? .init(id: .init(id: input.id)),
formSelection: { [weak self] _, variationId in
guard let variationId = variationId else { return }
self?.formSelectionDelegate?.formSelected(id: variationId)
},
completion: { variant in
...
}
)
  1. Create a new file for a custom ProductSummaryView (or use the existing) somewhere like Sources/ProductDetails/Views.
  2. Subclass the PoqProductSummaryView forward the delegate to your CustomWishlistWidget.
class CustomProductSummaryView: PoqProductSummaryView {
override func delegate(to delegate: ProductViewDelegate?) {
super.delegate(to: delegate)
(wishlistWidget as? CustomWishlistWidget)?.formSelectionDelegate = delegate
}
}
  1. Finally, inject your CustomProductSummaryView in the AppModule in the didAddToPlatform or setUpProductDetails function.
Container.shared.views.productSummaryView.replace { CustomProductSummaryView() }

Success! The customisation is complete and you can fully enjoy wishlisting variants.

Full Example Code