diff --git a/frontend/public/src/components/CourseProductDetailEnroll.js b/frontend/public/src/components/CourseProductDetailEnroll.js index 308c134fa4..7878d14953 100644 --- a/frontend/public/src/components/CourseProductDetailEnroll.js +++ b/frontend/public/src/components/CourseProductDetailEnroll.js @@ -67,7 +67,7 @@ type ProductDetailState = { destinationUrl: string } -export class CourseProductDetailEnroll extends React.Component< +export class CourseProductDetailEnroll extends React.PureComponent< Props, ProductDetailState > { @@ -79,6 +79,28 @@ export class CourseProductDetailEnroll extends React.Component< destinationUrl: "" } + constructor(props: Props) { + super(props) + + // Bind methods to avoid creating new functions on every render + this.toggleAddlProfileFieldsModal = this.toggleAddlProfileFieldsModal.bind(this) + this.toggleCartConfirmationDialogVisibility = this.toggleCartConfirmationDialogVisibility.bind(this) + this.onAddToCartClick = this.onAddToCartClick.bind(this) + this.redirectToCourseHomepage = this.redirectToCourseHomepage.bind(this) + this.saveProfile = this.saveProfile.bind(this) + this.cancelEnrollment = this.cancelEnrollment.bind(this) + + // Initialize memoization cache + this._computedCourseDataCache = null + this._lastCoursesRef = null + } + + componentWillUnmount() { + // Clean up cache references to prevent memory leaks + this._computedCourseDataCache = null + this._lastCoursesRef = null + } + toggleAddlProfileFieldsModal() { this.setState({ showAddlProfileFieldsModal: !this.state.showAddlProfileFieldsModal @@ -95,6 +117,7 @@ export class CourseProductDetailEnroll extends React.Component< window.open(target, "_blank") } } + toggleCartConfirmationDialogVisibility() { this.setState({ addedToCartDialogVisibility: !this.state.addedToCartDialogVisibility @@ -203,6 +226,56 @@ export class CourseProductDetailEnroll extends React.Component< }) } + // Memoize expensive computations to avoid recalculation on every render + getComputedCourseData = () => { + const { courses } = this.props + + // Return cached result if courses haven't changed + if (this._lastCoursesRef === courses && this._computedCourseDataCache) { + return this._computedCourseDataCache + } + + const courseRuns = courses && courses[0] ? courses[0].courseruns : null + const course = courses && courses[0] ? courses[0] : null + + const enrollableCourseRuns = courseRuns ? + courseRuns.filter( + (run: EnrollmentFlaggedCourseRun) => run.is_enrollable + ) : + [] + + const upgradableCourseRuns = enrollableCourseRuns.filter( + (run: EnrollmentFlaggedCourseRun) => run.is_upgradable + ) + + let run = null + let product = null + + if (courses && courseRuns) { + run = getFirstRelevantRun(courses[0], courseRuns) + if (run) { + product = run && run.products ? run.products[0] : null + this.updateDate(run) + } + } + + const computedData = { + course, + courseRuns, + enrollableCourseRuns, + upgradableCourseRuns, + run, + product, + hasMultipleEnrollableRuns: enrollableCourseRuns && enrollableCourseRuns.length > 1 + } + + // Cache the result + this._computedCourseDataCache = computedData + this._lastCoursesRef = courses + + return computedData + } + renderRunSelectorButtons( enrollableCourseRuns: Array ) { @@ -216,7 +289,7 @@ export class CourseProductDetailEnroll extends React.Component< )}