FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Convert the outline tab to React Query · Issue #1991 · openedx/frontend-app-learning · GitHub

Convert the outline tab to React Query #1991

Description

Part of #1975 (convert course-home tab data to React Query), Phase 3 of the epic #1946. Stacks directly on the dates-tab conversion (#1984), which set the pattern.

Summary

Convert the outline tab off Redux to React Query, following the self-wrapping pattern the dates tab established: OutlineTab renders <TabWithTimer> itself and owns its data via useCourseHomeMeta + a new useOutlineTabData hook (meta-tagged outline so the transitional model-store bridge keeps useModel('outline', …) populated for the whole widget/alert subtree). courseId comes from useParams, and the index.jsx route drops its <TabContainer tab="outline" fetch={fetchOutlineTab}> wrapper for a bare <OutlineTab />.

Because the route no longer runs fetchOutlineTab, state.courseHome.courseId is never set for this route, so every child that read courseId from the slice moves to useParams (the same class of breakage as the UpgradeToCompleteAlert slot-child in the dates PR). proctoringPanelStatus stays in Redux — it's client UI state, not server data.

Outline is the last <ShiftDatesAlert fetch={fetchOutlineTab}> caller, so this PR also finishes the alert's transitional bridge: ShiftDatesAlert drops its fetch/useDispatch and invalidates the date-bearing caches (datesTab + outlineTab) directly, and the fetchOutlineTab thunk is deleted.

Tasks

  • queryKeys.ts — add outlineTab(courseId).
  • apiHooks.ts — add useOutlineTabData(courseId) (getOutlineTabData, meta: { modelType: 'outline', courseId }).
  • OutlineTab.jsx — self-wrap TabWithTimer (activeTabSlug="outline", courseStatus={{ metadataQuery, tabDataQuery }}, metadataModel="courseHomeMeta"); courseId from useParams; keep proctoringPanelStatus on useSelector.
  • Move the subtree's courseId reads from useSelector(state.courseHome) to useParams: DateSummary, CourseDates, CourseHandouts, CourseTools, StartOrResumeCourseCard, WeeklyLearningGoalCard, ProctoringInfoPanel (keeps useDispatch).
  • ShiftDatesAlert — remove fetch prop + useDispatch; refreshTabData invalidates datesTab(courseId) and outlineTab(courseId).
  • index.jsx — outline route → <OutlineTab />; drop the fetchOutlineTab import/wiring.
  • Delete fetchOutlineTab (thunk + data/index.js re-export).
  • Tests: OutlineTab.test.jsx renders <OutlineTab /> directly (queries populate via mocked axios + bridge), mirroring the DatesTab.test.jsx shift; drop the now-dead Test fetchOutlineTab block from redux.test.js (generic fetchTab coverage remains via Test fetchProgressTab).

Notes / decisions

  • ShiftDatesAlert invalidates all date-bearing caches, not "the current tab". The alert is about dates; shifting them changes date info wherever it's cached — the dates model (datesTab query) and the outline model's datesWidget/SequenceDueDate (outlineTab query). So invalidation is model-independent: both keys, always. The dates PR invalidating only datesTab wasn't wrong — outline was still Redux/thunk-fetched then, so no outlineTab query existed to invalidate; its on-mount fetchOutlineTab handled staleness. This PR is where the outline cache enters the picture, so it's where it joins the alert's invalidation set.
  • proctoringPanelStatus stays in Redux. It's client UI state (a reducer flips it to loaded when ProctoringInfoPanel resolves), not server data, so it's out of scope for the data-layer conversion. OutlineTab keeps reading it from useSelector; only courseId moves.
  • Subtree courseId → useParams is required, not cosmetic. The slice's courseId is only ever set by a tab's fetch thunk (fetchTabRequest). Once the outline route stops running one, any descendant still reading state.courseHome.courseId gets undefined and its useModel(…, courseId) misses.

Note

The plan below was generated by Claude (Claude Code) and reviewed before posting.

Claude Plan — outline tab → React Query

Approach

A self-wrapping conversion mirroring the dates tab: the page owns its data via query hooks and renders <TabWithTimer> directly; TabContainer is untouched and simply stops wrapping this route. The model-store bridge (meta-tagged queries → addModel) keeps the large useModel('outline'|'courseHomeMeta', …) subtree working unchanged, so the conversion is behavior-preserving.

Data layer

// queryKeys.ts
outlineTab: (courseId: string) => [...courseHomeQueryKeys.all, 'outlineTab', courseId] as const,

// apiHooks.ts
export const useOutlineTabData = (courseId: string) => useQuery({
  queryKey: courseHomeQueryKeys.outlineTab(courseId),
  queryFn: () => getOutlineTabData(courseId),
  meta: { modelType: 'outline', courseId },
});

OutlineTab.jsx

const { courseId } = useParams();
const { proctoringPanelStatus } = useSelector(state => state.courseHome);
const metadataQuery = useCourseHomeMeta(courseId);
const tabDataQuery = useOutlineTabData(courseId);
// …existing useModel('courseHomeMeta'|'outline', courseId) reads unchanged…
return (
  <TabWithTimer
    activeTabSlug="outline"
    courseId={courseId}
    courseStatus={{ metadataQuery, tabDataQuery }}
    metadataModel="courseHomeMeta"
  >
    {/* existing outline body */}
  </TabWithTimer>
);

Subtree courseId reads → useParams

Each of these reads courseId from useSelector(state.courseHome) today and must move to useParams (the slice value goes unset once the route stops fetching via thunk):
DateSummary, widgets/CourseDates, widgets/CourseHandouts, widgets/CourseTools, widgets/StartOrResumeCourseCard, widgets/WeeklyLearningGoalCard, widgets/ProctoringInfoPanel (keeps its useDispatch for proctoring status). The section-outline subtree already uses useContextId() — untouched.

ShiftDatesAlert — finish the transitional bridge

const ShiftDatesAlert = ({ model }) => {
  const { courseId } = useParams();
  const queryClient = useQueryClient();
  // …useModel(model, courseId) visibility gate unchanged…
  const refreshTabData = () => {
    queryClient.invalidateQueries({ queryKey: courseHomeQueryKeys.datesTab(courseId) });
    queryClient.invalidateQueries({ queryKey: courseHomeQueryKeys.outlineTab(courseId) });
  };
  // onClick={() => resetDeadlines.mutate({ courseId, model }, { onSuccess: refreshTabData })}
};

fetch prop and useDispatch removed; both call sites (dates + outline) already pass no fetch.

Files

  • edit src/course-home/data/queryKeys.ts — outlineTab.
  • edit src/course-home/data/apiHooks.ts — useOutlineTabData.
  • edit src/course-home/outline-tab/OutlineTab.jsx — self-wrap; useParams; hooks.
  • edit DateSummary.jsx, widgets/{CourseDates,CourseHandouts,CourseTools,StartOrResumeCourseCard,WeeklyLearningGoalCard,ProctoringInfoPanel}.jsx — courseId → useParams.
  • edit src/course-home/suggested-schedule-messaging/ShiftDatesAlert.jsx — drop fetch/useDispatch; invalidate both date caches.
  • edit src/index.jsx — outline route → <OutlineTab />; drop fetchOutlineTab.
  • edit src/course-home/data/thunks.js + data/index.js — delete fetchOutlineTab + re-export.
  • edit src/course-home/outline-tab/OutlineTab.test.jsx — render <OutlineTab /> directly.
  • edit src/course-home/data/redux.test.js — drop Test fetchOutlineTab.

Verification

nvm use && npm run types && npm run lint && npm test (targeted: OutlineTab, ShiftDatesAlert, redux, then the suite), then npm run build. git grep -n "fetchOutlineTab" src → none. Manual smoke on the outline tab: renders, alerts/widgets populate, shift-dates click still refreshes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions


Back | FazBrowse Home | New Git URL