Skip to content

Commit f2aa85e

Browse files
authored
fix: Sync pagination state after course data fetch (#3065)
When a user navigates to page 2+ on the Studio course listing page and then navigates away (via browser back button or clicking the Studio logo), returning to the listing page loads page 1 content correctly but the pagination UI still highlights the previously selected page number. This creates a mismatch between the displayed content and the active pagination indicator. **Root cause:** The `fetchStudioHomeData` thunk fetches courses for a specific page but never syncs `currentPage` in the Redux store after a successful fetch. The `currentPage` was only being updated manually in `handlePageSelected` (on user click), but not when courses were fetched on component mount or other triggers. **Fix:** After successfully fetching course data in the thunk, dispatch `updateStudioHomeCoursesCustomParams({ currentPage: requestParams.page || 1 })` to keep the pagination UI state in sync with the actually fetched page.
1 parent 94900a0 commit f2aa85e

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

src/studio-home/data/thunks.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
updateLoadingStatuses,
1111
updateSavingStatuses,
1212
fetchCourseDataSuccessV2,
13+
updateStudioHomeCoursesCustomParams,
1314
} from './slice';
1415

1516
/**
@@ -43,6 +44,9 @@ function fetchStudioHomeData(
4344
try {
4445
const coursesData = await getStudioHomeCoursesV2(search || '', requestParams);
4546
dispatch(fetchCourseDataSuccessV2(coursesData));
47+
const searchPage = search ? parseInt(new URLSearchParams(search).get('page'), 10) : NaN;
48+
const fetchedPage = requestParams.page || (!Number.isNaN(searchPage) && searchPage > 0 ? searchPage : 1);
49+
dispatch(updateStudioHomeCoursesCustomParams({ currentPage: fetchedPage }));
4650
dispatch(updateLoadingStatuses({ courseLoadingStatus: RequestStatus.SUCCESSFUL }));
4751
} catch {
4852
dispatch(updateLoadingStatuses({ courseLoadingStatus: RequestStatus.FAILED }));
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import MockAdapter from 'axios-mock-adapter';
2+
import { initializeMockApp } from '@edx/frontend-platform';
3+
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
4+
5+
import { fetchStudioHomeData } from './thunks';
6+
import { getApiBaseUrl, getStudioHomeApiUrl } from './api';
7+
import {
8+
generateGetStudioCoursesApiResponseV2,
9+
generateGetStudioHomeDataApiResponse,
10+
} from '../factories/mockApiResponses';
11+
12+
let axiosMock;
13+
let dispatch;
14+
15+
describe('fetchStudioHomeData thunk', () => {
16+
beforeEach(() => {
17+
initializeMockApp({
18+
authenticatedUser: {
19+
userId: 3,
20+
username: 'abc123',
21+
administrator: true,
22+
roles: [],
23+
},
24+
});
25+
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
26+
dispatch = jest.fn();
27+
28+
axiosMock.onGet(getStudioHomeApiUrl()).reply(200, generateGetStudioHomeDataApiResponse());
29+
axiosMock.onGet(new RegExp(`${getApiBaseUrl()}/api/contentstore/v2/home/courses.*`))
30+
.reply(200, generateGetStudioCoursesApiResponseV2());
31+
});
32+
33+
afterEach(() => {
34+
jest.clearAllMocks();
35+
});
36+
37+
it('should sync currentPage from requestParams.page after fetching courses', async () => {
38+
const requestParams = { page: 3 };
39+
await fetchStudioHomeData('', false, requestParams)(dispatch);
40+
41+
const updateParamsCall = dispatch.mock.calls.find(
42+
([action]) => action.type === 'studioHome/updateStudioHomeCoursesCustomParams',
43+
);
44+
expect(updateParamsCall).toBeDefined();
45+
expect(updateParamsCall[0].payload).toEqual({ currentPage: 3 });
46+
});
47+
48+
it('should sync currentPage from search query string when page is not in requestParams', async () => {
49+
await fetchStudioHomeData('?page=2', false, {})(dispatch);
50+
51+
const updateParamsCall = dispatch.mock.calls.find(
52+
([action]) => action.type === 'studioHome/updateStudioHomeCoursesCustomParams',
53+
);
54+
expect(updateParamsCall).toBeDefined();
55+
expect(updateParamsCall[0].payload).toEqual({ currentPage: 2 });
56+
});
57+
58+
it('should default currentPage to 1 when page is not specified anywhere', async () => {
59+
await fetchStudioHomeData('', false, {})(dispatch);
60+
61+
const updateParamsCall = dispatch.mock.calls.find(
62+
([action]) => action.type === 'studioHome/updateStudioHomeCoursesCustomParams',
63+
);
64+
expect(updateParamsCall).toBeDefined();
65+
expect(updateParamsCall[0].payload).toEqual({ currentPage: 1 });
66+
});
67+
68+
it('should prefer requestParams.page over search query string page', async () => {
69+
const requestParams = { page: 5 };
70+
await fetchStudioHomeData('?page=2', false, requestParams)(dispatch);
71+
72+
const updateParamsCall = dispatch.mock.calls.find(
73+
([action]) => action.type === 'studioHome/updateStudioHomeCoursesCustomParams',
74+
);
75+
expect(updateParamsCall).toBeDefined();
76+
expect(updateParamsCall[0].payload).toEqual({ currentPage: 5 });
77+
});
78+
});

0 commit comments

Comments
 (0)