import React , { Component , ComponentClass } from 'react' ; import { compose } from 'redux' ; import { cloneDeep , isEqual , set } from 'lodash-es' ; import { CommerceTypes , FetchDataFunction , ReviewDataSource , ReviewTypes , withCommerceData , WithCommerceProps , WithCommerceProviderProps } from '@brandingbrand/fscommerce' ; export type CommerceToReviewMapFunction < T extends CommerceTypes . Product = CommerceTypes . Product > = ( product : T ) => string ; template T export interface WithProductDetailProviderProps < T extends CommerceTypes . Product = CommerceTypes . Product > extends WithCommerceProviderProps < T > { commerceToReviewMap : keyof T | CommerceToReviewMapFunction < T > ; reviewDataSource ? : ReviewDataSource ; } template T export type WithProductDetailProps < T extends CommerceTypes . Product = CommerceTypes . Product > = WithCommerceProps < T > & { reviewsData ? : ReviewTypes . ReviewDetails [ ] ; } ; template T export type WithProductDetailState < T extends CommerceTypes . Product = CommerceTypes . Product > = Pick < WithCommerceProps < T > , 'commerceData' > & { reviewsData ? : ReviewTypes . ReviewDetails [ ] ; } ; template T param { ComponentClass < P & WithProductDetailProps > } WrappedComponent returns { ComponentClass < P & WithProductDetailProviderProps > } export type ProductDetailWrapper < P , T extends CommerceTypes . Product = CommerceTypes . Product > = ( WrappedComponent : ComponentClass < P & WithProductDetailProps < T > > ) => ComponentClass < P & WithProductDetailProviderProps < T > > ; template P template T param { FetchDataFunction < P , T > } fetchProduct returns { ProductDetailWrapper < P > } export default function withProductDetailData < P , T extends CommerceTypes . Product = CommerceTypes . Product > ( fetchProduct ) { type ResultProps = P & WithProductDetailProviderProps < T > & WithCommerceProps < T > ; param { ComponentClass < P & WithProductDetailProps > } WrappedComponent returns { ComponentClass < P & WithProductDetailProviderProps > } return ( WrappedComponent ) => { class ProductDetailProvider extends Component < ResultProps , WithProductDetailState < T > > { async componentDidUpdate ( prevProps ) { const { commerceToReviewMap , reviewDataSource } = this . props ; const commerceData = this . props . commerceData as T | undefined ; if ( commerceData === undefined || reviewDataSource === undefined ) { return ; } if ( ! isEqual ( prevProps . commerceData , commerceData ) ) { const ids = reviewDataSource . productIdMapper < T > ( [ commerceData ] , commerceToReviewMap ) ; const reviewsData = await reviewDataSource . fetchReviewDetails ( { ids } ) ; const newCommerceData = cloneDeep ( commerceData ) ; set ( newCommerceData , 'review' , reviewsData [ 0 ] ) ; this . setState ( { commerceData : newCommerceData , reviewsData } ) ; } } render ( ) { const { commerceToReviewMap , ... props } = this . props as any ; return ( < WrappedComponent { ... props } commerceData = { ( this . state && this . state . commerceData ) || this . props . commerceData } reviewsData = { ( this . state && this . state . reviewsData ) } />); } } return compose < ComponentClass < P & WithProductDetailProviderProps < T > > > ( withCommerceData < P , T > ( fetchProduct ) ) ( ProductDetailProvider ) ; } ; }