Skip to content

Commit 48b58c6

Browse files
refactor: simplify Library Context
1 parent b177238 commit 48b58c6

14 files changed

+91
-85
lines changed

src/library-authoring/EmptyStates.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useParams } from 'react-router';
21
import { FormattedMessage } from '@edx/frontend-platform/i18n';
32
import type { MessageDescriptor } from 'react-intl';
43
import {
@@ -8,6 +7,7 @@ import { Add } from '@openedx/paragon/icons';
87
import { ClearFiltersButton } from '../search-manager';
98
import messages from './messages';
109
import { useContentLibrary } from './data/apiHooks';
10+
import { useLibraryContext } from './common/context';
1111

1212
export const NoComponents = ({
1313
infoText = messages.noComponents,
@@ -18,7 +18,7 @@ export const NoComponents = ({
1818
addBtnText?: MessageDescriptor;
1919
handleBtnClick: () => void;
2020
}) => {
21-
const { libraryId } = useParams();
21+
const { libraryId } = useLibraryContext();
2222
const { data: libraryData } = useContentLibrary(libraryId);
2323
const canEditLibrary = libraryData?.canEditLibrary ?? false;
2424

src/library-authoring/LibraryAuthoringPage.tsx

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useEffect } from 'react';
1+
import React, { useEffect } from 'react';
22
import { Helmet } from 'react-helmet';
33
import classNames from 'classnames';
44
import { StudioFooter } from '@edx/frontend-component-footer';
@@ -13,7 +13,7 @@ import {
1313
} from '@openedx/paragon';
1414
import { Add, InfoOutline } from '@openedx/paragon/icons';
1515
import {
16-
Routes, Route, useLocation, useNavigate, useParams, useSearchParams,
16+
Routes, Route, useLocation, useNavigate, useSearchParams,
1717
} from 'react-router-dom';
1818

1919
import Loading from '../generic/Loading';
@@ -33,7 +33,7 @@ import LibraryCollections from './collections/LibraryCollections';
3333
import LibraryHome from './LibraryHome';
3434
import { useContentLibrary } from './data/apiHooks';
3535
import { LibrarySidebar } from './library-sidebar';
36-
import { LibraryContext, SidebarBodyComponentId } from './common/context';
36+
import { SidebarBodyComponentId, useLibraryContext } from './common/context';
3737
import messages from './messages';
3838

3939
enum TabList {
@@ -53,7 +53,7 @@ const HeaderActions = ({ canEditLibrary }: HeaderActionsProps) => {
5353
openInfoSidebar,
5454
closeLibrarySidebar,
5555
sidebarBodyComponent,
56-
} = useContext(LibraryContext);
56+
} = useLibraryContext();
5757

5858
if (!canEditLibrary) {
5959
return null;
@@ -119,19 +119,15 @@ const LibraryAuthoringPage = () => {
119119
const location = useLocation();
120120
const navigate = useNavigate();
121121

122-
const { libraryId } = useParams();
123-
if (!libraryId) {
124-
// istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker.
125-
throw new Error('Rendered without libraryId URL parameter');
126-
}
122+
const { libraryId } = useLibraryContext();
127123
const { data: libraryData, isLoading } = useContentLibrary(libraryId);
128124

129125
const currentPath = location.pathname.split('/').pop();
130126
const activeKey = (currentPath && currentPath in TabList) ? TabList[currentPath] : TabList.home;
131127
const {
132128
sidebarBodyComponent,
133129
openInfoSidebar,
134-
} = useContext(LibraryContext);
130+
} = useLibraryContext();
135131

136132
useEffect(() => {
137133
openInfoSidebar();
@@ -196,16 +192,12 @@ const LibraryAuthoringPage = () => {
196192
<Route
197193
path={TabList.home}
198194
element={(
199-
<LibraryHome
200-
libraryId={libraryId}
201-
tabList={TabList}
202-
handleTabChange={handleTabChange}
203-
/>
195+
<LibraryHome tabList={TabList} handleTabChange={handleTabChange} />
204196
)}
205197
/>
206198
<Route
207199
path={TabList.components}
208-
element={<LibraryComponents libraryId={libraryId} variant="full" />}
200+
element={<LibraryComponents variant="full" />}
209201
/>
210202
<Route
211203
path={TabList.collections}

src/library-authoring/LibraryHome.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import React, { useContext } from 'react';
21
import { Stack } from '@openedx/paragon';
32
import { useIntl } from '@edx/frontend-platform/i18n';
43

@@ -9,22 +8,21 @@ import { LibraryComponents } from './components';
98
import LibrarySection from './components/LibrarySection';
109
import LibraryRecentlyModified from './LibraryRecentlyModified';
1110
import messages from './messages';
12-
import { LibraryContext } from './common/context';
11+
import { useLibraryContext } from './common/context';
1312

1413
type LibraryHomeProps = {
15-
libraryId: string,
1614
tabList: { home: string, components: string, collections: string },
1715
handleTabChange: (key: string) => void,
1816
};
1917

20-
const LibraryHome = ({ libraryId, tabList, handleTabChange } : LibraryHomeProps) => {
18+
const LibraryHome = ({ tabList, handleTabChange } : LibraryHomeProps) => {
2119
const intl = useIntl();
2220
const {
2321
totalHits: componentCount,
2422
totalCollectionHits: collectionCount,
2523
isFiltered,
2624
} = useSearchContext();
27-
const { openAddContentSidebar } = useContext(LibraryContext);
25+
const { openAddContentSidebar } = useLibraryContext();
2826

2927
const renderEmptyState = () => {
3028
if (componentCount === 0 && collectionCount === 0) {
@@ -35,7 +33,7 @@ const LibraryHome = ({ libraryId, tabList, handleTabChange } : LibraryHomeProps)
3533

3634
return (
3735
<Stack gap={3}>
38-
<LibraryRecentlyModified libraryId={libraryId} />
36+
<LibraryRecentlyModified />
3937
{
4038
renderEmptyState()
4139
|| (
@@ -52,7 +50,7 @@ const LibraryHome = ({ libraryId, tabList, handleTabChange } : LibraryHomeProps)
5250
contentCount={componentCount}
5351
viewAllAction={() => handleTabChange(tabList.components)}
5452
>
55-
<LibraryComponents libraryId={libraryId} variant="preview" />
53+
<LibraryComponents variant="preview" />
5654
</LibrarySection>
5755
</>
5856
)

src/library-authoring/LibraryRecentlyModified.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@ import messages from './messages';
1010
import ComponentCard from './components/ComponentCard';
1111
import { useLibraryBlockTypes } from './data/apiHooks';
1212
import CollectionCard from './components/CollectionCard';
13+
import { useLibraryContext } from './common/context';
1314

14-
const RecentlyModified: React.FC<{ libraryId: string }> = ({ libraryId }) => {
15+
const RecentlyModified: React.FC<Record<never, never>> = () => {
1516
const intl = useIntl();
1617
const {
1718
hits,
1819
collectionHits,
1920
totalHits,
2021
totalCollectionHits,
2122
} = useSearchContext();
23+
const { libraryId } = useLibraryContext();
2224

2325
const componentCount = totalHits + totalCollectionHits;
2426
// Since we only display a fixed number of items in preview,
@@ -77,13 +79,16 @@ const RecentlyModified: React.FC<{ libraryId: string }> = ({ libraryId }) => {
7779
: null;
7880
};
7981

80-
const LibraryRecentlyModified: React.FC<{ libraryId: string }> = ({ libraryId }) => (
81-
<SearchContextProvider
82-
extraFilter={`context_key = "${libraryId}"`}
83-
overrideSearchSortOrder={SearchSortOption.RECENTLY_MODIFIED}
84-
>
85-
<RecentlyModified libraryId={libraryId} />
86-
</SearchContextProvider>
87-
);
82+
const LibraryRecentlyModified: React.FC<Record<never, never>> = () => {
83+
const { libraryId } = useLibraryContext();
84+
return (
85+
<SearchContextProvider
86+
extraFilter={`context_key = "${libraryId}"`}
87+
overrideSearchSortOrder={SearchSortOption.RECENTLY_MODIFIED}
88+
>
89+
<RecentlyModified />
90+
</SearchContextProvider>
91+
);
92+
};
8893

8994
export default LibraryRecentlyModified;

src/library-authoring/add-content/AddContentContainer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import { useCopyToClipboard } from '../../generic/clipboard';
2323
import { getCanEdit } from '../../course-unit/data/selectors';
2424
import { useCreateLibraryBlock, useLibraryPasteClipboard, useUpdateCollectionComponents } from '../data/apiHooks';
2525
import { getEditUrl } from '../components/utils';
26+
import { useLibraryContext } from '../common/context';
2627

2728
import messages from './messages';
28-
import { LibraryContext } from '../common/context';
2929

3030
type ContentType = {
3131
name: string,
@@ -73,7 +73,7 @@ const AddContentContainer = () => {
7373
const { showPasteXBlock } = useCopyToClipboard(canEdit);
7474
const {
7575
openCreateCollectionModal,
76-
} = React.useContext(LibraryContext);
76+
} = useLibraryContext();
7777

7878
const collectionButtonData = {
7979
name: intl.formatMessage(messages.collectionButton),

src/library-authoring/collections/LibraryCollectionComponents.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import { useContext } from 'react';
21
import { Stack } from '@openedx/paragon';
32
import { NoComponents, NoSearchResults } from '../EmptyStates';
43
import { useSearchContext } from '../../search-manager';
54
import { LibraryComponents } from '../components';
65
import messages from './messages';
7-
import { LibraryContext } from '../common/context';
6+
import { useLibraryContext } from '../common/context';
87

9-
const LibraryCollectionComponents = ({ libraryId }: { libraryId: string }) => {
8+
const LibraryCollectionComponents = () => {
109
const { totalHits: componentCount, isFiltered } = useSearchContext();
11-
const { openAddContentSidebar } = useContext(LibraryContext);
10+
const { openAddContentSidebar } = useLibraryContext();
1211

1312
if (componentCount === 0) {
1413
return isFiltered
@@ -25,7 +24,7 @@ const LibraryCollectionComponents = ({ libraryId }: { libraryId: string }) => {
2524
return (
2625
<Stack direction="vertical" gap={3}>
2726
<h3 className="text-gray">Content ({componentCount})</h3>
28-
<LibraryComponents libraryId={libraryId} variant="full" />
27+
<LibraryComponents variant="full" />
2928
</Stack>
3029
);
3130
};

src/library-authoring/collections/LibraryCollectionPage.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useContext, useEffect } from 'react';
1+
import { useEffect } from 'react';
22
import { StudioFooter } from '@edx/frontend-component-footer';
33
import { useIntl } from '@edx/frontend-platform/i18n';
44
import {
@@ -27,7 +27,7 @@ import {
2727
SearchSortWidget,
2828
} from '../../search-manager';
2929
import { useCollection, useContentLibrary } from '../data/apiHooks';
30-
import { LibraryContext } from '../common/context';
30+
import { useLibraryContext } from '../common/context';
3131
import messages from './messages';
3232
import { LibrarySidebar } from '../library-sidebar';
3333
import LibraryCollectionComponents from './LibraryCollectionComponents';
@@ -36,7 +36,7 @@ const HeaderActions = ({ canEditLibrary }: { canEditLibrary: boolean; }) => {
3636
const intl = useIntl();
3737
const {
3838
openAddContentSidebar,
39-
} = useContext(LibraryContext);
39+
} = useLibraryContext();
4040

4141
if (!canEditLibrary) {
4242
return null;
@@ -104,7 +104,7 @@ const LibraryCollectionPage = () => {
104104
const {
105105
sidebarBodyComponent,
106106
openCollectionInfoSidebar,
107-
} = useContext(LibraryContext);
107+
} = useLibraryContext();
108108

109109
const {
110110
data: collectionData,
@@ -189,7 +189,7 @@ const LibraryCollectionPage = () => {
189189
<div className="flex-grow-1" />
190190
<SearchSortWidget />
191191
</div>
192-
<LibraryCollectionComponents libraryId={libraryId} />
192+
<LibraryCollectionComponents />
193193
</SearchContextProvider>
194194
</Container>
195195
<StudioFooter />

src/library-authoring/collections/LibraryCollections.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import { useContext } from 'react';
2-
31
import { useLoadOnScroll } from '../../hooks';
42
import { useSearchContext } from '../../search-manager';
53
import { NoComponents, NoSearchResults } from '../EmptyStates';
64
import CollectionCard from '../components/CollectionCard';
75
import { LIBRARY_SECTION_PREVIEW_LIMIT } from '../components/LibrarySection';
86
import messages from './messages';
9-
import { LibraryContext } from '../common/context';
7+
import { useLibraryContext } from '../common/context';
108

119
type LibraryCollectionsProps = {
1210
variant: 'full' | 'preview',
@@ -29,7 +27,7 @@ const LibraryCollections = ({ variant }: LibraryCollectionsProps) => {
2927
isFiltered,
3028
} = useSearchContext();
3129

32-
const { openCreateCollectionModal } = useContext(LibraryContext);
30+
const { openCreateCollectionModal } = useLibraryContext();
3331

3432
const collectionList = variant === 'preview' ? collectionHits.slice(0, LIBRARY_SECTION_PREVIEW_LIMIT) : collectionHits;
3533

src/library-authoring/common/context.tsx

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useToggle } from '@openedx/paragon';
22
import React from 'react';
3+
import { useParams } from 'react-router-dom';
34

45
export enum SidebarBodyComponentId {
56
AddContent = 'add-content',
@@ -9,36 +10,44 @@ export enum SidebarBodyComponentId {
910
}
1011

1112
export interface LibraryContextData {
13+
/** The ID of the current library */
14+
libraryId: string;
15+
// Sidebar stuff - only one sidebar is active at any given time:
1216
sidebarBodyComponent: SidebarBodyComponentId | null;
1317
closeLibrarySidebar: () => void;
1418
openAddContentSidebar: () => void;
1519
openInfoSidebar: () => void;
1620
openComponentInfoSidebar: (usageKey: string) => void;
1721
currentComponentUsageKey?: string;
22+
// "Create New Collection" modal
1823
isCreateCollectionModalOpen: boolean;
1924
openCreateCollectionModal: () => void;
2025
closeCreateCollectionModal: () => void;
26+
// Current collection
2127
openCollectionInfoSidebar: (collectionId: string) => void;
2228
currentCollectionId?: string;
2329
}
2430

25-
export const LibraryContext = React.createContext({
26-
sidebarBodyComponent: null,
27-
closeLibrarySidebar: () => {},
28-
openAddContentSidebar: () => {},
29-
openInfoSidebar: () => {},
30-
openComponentInfoSidebar: (_usageKey: string) => {}, // eslint-disable-line @typescript-eslint/no-unused-vars
31-
isCreateCollectionModalOpen: false,
32-
openCreateCollectionModal: () => {},
33-
closeCreateCollectionModal: () => {},
34-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
35-
openCollectionInfoSidebar: (_collectionId: string) => {},
36-
} as LibraryContextData);
31+
/**
32+
* Library Context.
33+
* Always available when we're in the context of a single library.
34+
*
35+
* Get this using `useLibraryContext()`
36+
*
37+
* Not used on the "library list" on Studio home.
38+
*/
39+
const LibraryContext = React.createContext<LibraryContextData | undefined>(undefined);
3740

3841
/**
3942
* React component to provide `LibraryContext`
4043
*/
4144
export const LibraryProvider = (props: { children?: React.ReactNode }) => {
45+
const { libraryId } = useParams();
46+
47+
if (libraryId === undefined) {
48+
// istanbul ignore next - This shouldn't be possible; it's just here to satisfy the type checker.
49+
throw new Error('Error: route is missing libraryId.');
50+
}
4251
const [sidebarBodyComponent, setSidebarBodyComponent] = React.useState<SidebarBodyComponentId | null>(null);
4352
const [currentComponentUsageKey, setCurrentComponentUsageKey] = React.useState<string>();
4453
const [currentCollectionId, setcurrentCollectionId] = React.useState<string>();
@@ -76,7 +85,8 @@ export const LibraryProvider = (props: { children?: React.ReactNode }) => {
7685
setSidebarBodyComponent(SidebarBodyComponentId.CollectionInfo);
7786
}, []);
7887

79-
const context = React.useMemo(() => ({
88+
const context = React.useMemo<LibraryContextData>(() => ({
89+
libraryId,
8090
sidebarBodyComponent,
8191
closeLibrarySidebar,
8292
openAddContentSidebar,
@@ -89,6 +99,7 @@ export const LibraryProvider = (props: { children?: React.ReactNode }) => {
8999
openCollectionInfoSidebar,
90100
currentCollectionId,
91101
}), [
102+
libraryId,
92103
sidebarBodyComponent,
93104
closeLibrarySidebar,
94105
openAddContentSidebar,
@@ -108,3 +119,11 @@ export const LibraryProvider = (props: { children?: React.ReactNode }) => {
108119
</LibraryContext.Provider>
109120
);
110121
};
122+
123+
export function useLibraryContext(): LibraryContextData {
124+
const ctx = React.useContext(LibraryContext);
125+
if (ctx === undefined) {
126+
throw new Error('useLibraryContext() was used in a component without a <LibraryProvider> ancestor.');
127+
}
128+
return ctx;
129+
}

0 commit comments

Comments
 (0)