openedx / openedx/frontend-app-learning

Convert course-home tab data to React Query

Open
#1,975 0 comments 0 reactions 1 assignee View on GitHub

@brian-smith-tcril is already working on this.

Since Aug 5, 2026.

Dominant language
JavaScript
Stars
70
Forks
335
Avg merge
1d 17h
Merged PRs (30d)
35

Description

Part of #1946 — Redux → React Query migration (Stage 1).

Goal: all course-tab data fetching on React Query; remove the courseHome reducer.

Two prerequisites were tracked as their own sub-issues: courseware search (#1979) and the CTA toast → ToastProvider (#1980) — the shared TabPage had to be off Redux before the tabs converted.

Done (each its own sub-issue of #1946, stacked in #2062)

  • The five tabs: dates #1984, outline #1991 (incl. dismissWelcomeMessage), progress #1998, live #2002, discussion #2003 — the fetchTab machinery and TabContainer.jsx orchestration are gone.
  • requestCert → mutation (#1994).
  • fetchExamAttemptsDatauseExamAttemptsData + the plugin-facing useExamsData() (#2075 → PR #2077); split out because courseHome.examsData was a plugin-facing read (PR #1829), so its removal is a breaking change on its own.
  • course-home/data/thunks.js, redux.test.js, slice.test.js deleted along the way; api.test.js and the Pact test (course-home/data/pact-tests/lmsPact.test.jsx) stay — they test api.js, which is unchanged.

Remaining — one layer, closes this issue

  • The slice is down to one live field, proctoringPanelStatus: written by ProctoringInfoPanel when its getProctoringInfoData fetch settles, read by OutlineTab (defers the weekly-goal card) and ProductTours (holds the outline tour until the card exists). Four other initial-state fields (courseStatus, courseId, errorMessage, errorCode) are dead.
  • Convert getProctoringInfoData to a useProctoringInfoData query; both readers read "settled" off the query's status (same key → one fetch); the panel derives its display state from data instead of six useStates.
  • Delete slice.js and course-home/data/index.js; drop courseHome from store.ts and setupTest.js's initializeTestStore.

Verify: courseHome reducer gone (store.ts = models + specialExams + plugins); outline tab still defers the weekly-goal card until the proctoring panel settles; outline-tab tour still waits for it; git grep courseHome src/store.ts src/setupTest.js empty; Pact test green.

[!NOTE]
The plan below was written by Claude (Claude Code) and reviewed before posting.

Plan for the close-out layer

Why a query, not lifted state

The Redux field is a hand-rolled loading flag for one fetch. The tab conversions already replaced such flags (courseStatus strings) with query state, so the same move applies: make the fetch a query and read isPending off it. Both readers call the hook; React Query dedupes by key; no new context or lifted useState.

Pieces

  1. course-home/data/queryKeys.ts + apiHooks.tsproctoringInfo(courseId, username) key; useProctoringInfoData(courseId, username, enabled = true) wrapping the unchanged getProctoringInfoData (its 404 → {} mapping stays in the api).
  2. ProctoringInfoPanel.jsx — replace the mount effect + six useStates with the query and plain derivation from data (status, link, expiry → readable status, release date, past-due, show-panel = non-empty response). The response is raw snake_case today; keep the reads or camelCase in the hook — same rendered output either way. useDispatch, the slice import, and the exhaustive-deps disable leave.
  3. OutlineTab.jsx(!enableProctoredExams || proctoringPanelStatus === 'loaded')(!enableProctoredExams || !isPending) from the same hook (username already in scope from courseHomeMeta).
  4. ProductTours.jsx — same hook with enabled: outlineTabActive (the panel only ever mounted on the outline tab, so no new request elsewhere). Username must be the courseHomeMeta one the panel keys on (masquerade-aware), not getAuthenticatedUser().username, or the query key forks under masquerade — LoadedTabPage already reads courseHomeMeta and passes it as a prop (revisit at code review).
  5. Delete slice.js, course-home/data/index.js; drop courseHome from store.ts and initializeTestStore. Check whether LOADED in constants.ts loses its last importer.

Settled-vs-error (decided: !isPending + retry: false)

The panel's .catch(() => {}) and .finally(resolve) were added together in PR #727 (2021) so the goals widget would never stay hidden because of this request — settling on any outcome is the intent. The catch's "API throws 404" comment was already wrong when written (the api had mapped 404 → {} since the panel's first commits), and before #727 a 5xx surfaced as an unhandled rejection, so silencing real failures was a side effect, not a choice. The query keeps the intent and drops the accident: readers gate on !isPending (never isSuccess); retry: false so a failure settles immediately as #727 designed rather than after the app default's ~7s of 5xx retries; no swallow in queryFn, so the error reaches the app QueryCache.onError and is logged. Rendered output on error is unchanged (no data → panel hidden).

Tests

  • apiHooks.test.tsx: payload resolves; 404 → {}; enabled: false idle; non-404 → isError.
  • OutlineTab.test.jsx: the existing proctoring block renders the panel against a mocked endpoint and should pass unchanged; add a weekly-goal-deferral case if none asserts the gate today.
  • ProductTours.test.jsx: already mocks the proctoring URL (404) so the outline tour proceeds; passes once the hook keys on the same username the mock uses.

Commit

refactor: retire the courseHome Redux slice — Closes #1975. Not breaking: proctoringPanelStatus was never a documented or plugin-facing surface.

After this layer

Stack #2062 layer 16. Then #2017 (plugins reducer) and #1977 (models reducer), leaving store.ts at specialExams only — #1978's endpoint.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.