feat: move discussion topic prefetch from trigger to widget config lifecycle#1897
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1897 +/- ##
=======================================
Coverage 91.27% 91.28%
=======================================
Files 343 343
Lines 5766 5770 +4
Branches 1387 1388 +1
=======================================
+ Hits 5263 5267 +4
Misses 484 484
Partials 19 19 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR moves Discussions data loading (getCourseDiscussionTopics) out of the DiscussionsTrigger component and into a new optional prefetch hook on sidebar widget configs, executed by SidebarContextProvider when the provider mounts and when course metadata updates.
Changes:
- Add
widget.prefetch({ courseId, course, dispatch })execution inSidebarContextProvider. - Introduce
discussionsPrefetch+prefetchin the discussions widget config; simplifyDiscussionsTriggerto render-only. - Update tests and docs to reflect the new widget prefetch lifecycle; add a new unit test file for
Sidebar.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/courseware/course/sidebar/SidebarContextProvider.jsx | Runs prefetch for enabled widgets via a new effect. |
| src/widgets/discussions/widgetConfig.js | Adds discussionsPrefetch and wires it into the widget config. |
| src/widgets/discussions/DiscussionsTrigger.jsx | Removes fetching logic; now reads the model and renders conditionally. |
| src/widgets/discussions/DiscussionsTrigger.test.jsx | Pre-populates the store by executing the thunk in beforeEach. |
| src/courseware/course/sidebar/SidebarContextProvider.test.jsx | Mocks useDispatch/useModel and adjusts UC7 toggle expectations. |
| src/courseware/course/sidebar/Sidebar.test.jsx | Adds unit tests for Sidebar rendering/switching behavior. |
| src/courseware/course/sidebar/README.md | Documents the new prefetch widget config field. |
| src/courseware/course/sidebar/ARCHITECTURE.md | Documents the provider’s prefetch responsibility and lifecycle. |
| src/courseware/course/sidebar/USE_CASE_VERIFICATION.md | Updates the architecture diagram to include a prefetch effect. |
| src/widgets/discussions/README.md | Documents discussions-specific prefetch behavior and exports. |
Comments suppressed due to low confidence (1)
src/widgets/discussions/DiscussionsTrigger.jsx:13
- DiscussionsTrigger no longer reads DISCUSSIONS_MFE_BASE_URL, but it still calls ensureConfig(['DISCUSSIONS_MFE_BASE_URL']) at module load. This keeps an import-time hard dependency on that config even when the trigger is meant to be a pure render component. Consider moving the ensureConfig call to where the config is actually used now (e.g., discussionsPrefetch and/or DiscussionsSidebar) and removing it from the trigger.
import { ensureConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon } from '@openedx/paragon';
import { QuestionAnswer } from '@openedx/paragon/icons';
import PropTypes from 'prop-types';
import { useContext } from 'react';
import { useModel } from '@src/generic/model-store';
import { WIDGETS } from '@src/constants';
import SidebarTriggerBase from '@src/courseware/course/sidebar/common/TriggerBase';
import SidebarContext from '@src/courseware/course/sidebar/SidebarContext';
import messages from './messages';
ensureConfig(['DISCUSSIONS_MFE_BASE_URL']);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
arbrandes
left a comment
There was a problem hiding this comment.
At first glance this makes a lot of sense: centralizing data prefetch in the provider so that isAvailable checks can rely on pre-populated Redux state. But there's an issue:
The prefetch effect at SidebarContextProvider.jsx#L89-L96 includes coursewareMeta and courseHomeMeta in its dependency array. The two models settle in separate React batches during initial load, which causes the effect to fire twice after the guard in discussionsPrefetch starts passing.
I confirmed this produces duplicate requests to both GET /api/discussion/v1/courses/{courseId} and GET /api/discussion/v2/course_topics/{courseId} on every page load - two requests to each endpoint returning identical data (thunks.js#L268-L286).
The old code in DiscussionsTrigger avoided this because its effect depended on [courseId, baseUrl, edxProvider, dispatch], all of which were stable after the initial render.
A simple fix might be to remove coursewareMeta and courseHomeMeta from the dependency array. The effect only needs to run once per courseId - the course object is only used inside discussionsPrefetch to check tabs, and that check can happen with the latest value via a ref or by passing the models lazily.
arbrandes
left a comment
There was a problem hiding this comment.
Ok, the ref workaround solves it, but it's a bit hacky. The real solution is going to be moving to React Query, later on.
Let's worry about that later, though! 👍🏼
…onfig lifecycle (openedx#1897)" This reverts commit 59113b0.
Reverts the following two commits: - 59113b0 feat: move discussion topic prefetch from trigger to widget config lifecycle (openedx#1897) - 0664dc3 feat: decouple notifications panel using widget registry mechanism (openedx#1885) Co-Authored-By: Claude <noreply@anthropic.com>
Summary
Moves data-fetching logic (
getCourseDiscussionTopicsdispatch) out ofDiscussionsTriggerand into a newprefetchfield on the widget config.SidebarContextProvidernow runswidget.prefetch()for all enabled widgets on mount and when course metadata changes, ensuring Redux store data is populated beforeisAvailablechecks and component rendering.Motivation
Previously,
DiscussionsTriggerwas responsible for both rendering and fetching discussion topics. This created a coupling between the trigger component and the data lifecycle. The trigger had to mount before data was available, causing timing issues with widget availability checks. Moving prefetch to the framework level ensures data is ready before any widget logic runs.Changes
Code (5 files)
SidebarContextProvider.jsx: AddeduseDispatch+useEffectthat iteratesenabledWidgetsand callswidget.prefetch({ courseId, course, dispatch })when course metadata changes.DiscussionsTrigger.jsx: RemoveduseDispatch,useEffect,getCourseDiscussionTopics,courseHomeMetatabs lookup, andgetConfig().DISCUSSIONS_MFE_BASE_URLcheck. Now a pure render component that only reads thediscussionTopicsmodel.widgetConfig.js: AddeddiscussionsPrefetchfunction andprefetchfield to the discussions widget config.SidebarContextProvider.test.jsx: Addedreact-reduxmock, enricheduseModelmock forcourseHomeMeta, updated UC7 toggle tests to reflect mobile behavior (no auto-open).DiscussionsTrigger.test.jsx: AddedexecuteThunk(getCourseDiscussionTopics(...))inbeforeEachto pre-populate the store.Documentation (4 files)
ARCHITECTURE.md: Documented the prefetch lifecycle and updated provider responsibilities.README.md: Addedprefetchto the widget structure definition and usage guide.USE_CASE_VERIFICATION.md: Added Prefetch Effect (effect #0) to the architecture diagram.discussions/README.md: AddeddiscussionsPrefetchexport and data prefetch documentation.Widget Config API Addition
The
prefetchfield is a new optional field on the widget config: