@@ -33,6 +33,7 @@ type DataContextValues = {
3333 hasMore : boolean ;
3434 isLoadingMore : boolean ;
3535 fullLoadProgress : string | null ;
36+ dataLoadError : Error | null ;
3637 loadNextPage : ( ) => Promise < void > ;
3738 ensureFullExplorerData : ( ) => Promise < void > ;
3839} ;
@@ -71,12 +72,14 @@ export const DataProvider = ({ children }: { children: React.ReactNode }) => {
7172 const [ isLoadingMore , setIsLoadingMore ] = useState < boolean > ( false ) ;
7273 const [ isFullStoreLoaded , setIsFullStoreLoaded ] = useState < boolean > ( false ) ;
7374 const [ fullLoadProgress , setFullLoadProgress ] = useState < string | null > ( null ) ;
75+ const [ dataLoadError , setDataLoadError ] = useState < Error | null > ( null ) ;
7476
7577 const dataStoreRef = useRef ( dataStore ) ;
7678 const loadedPagesRef = useRef ( loadedPages ) ;
7779 const totalPagesRef = useRef ( totalPages ) ;
7880 const isFullStoreLoadedRef = useRef ( isFullStoreLoaded ) ;
7981 const loadingPageRef = useRef ( false ) ;
82+ const loadChainRef = useRef ( Promise . resolve < Record < string , TreeDocument > > ( { } ) ) ;
8083 const fullLoadRef = useRef < Promise < void > | null > ( null ) ;
8184 const bootstrapDoneRef = useRef ( false ) ;
8285
@@ -102,23 +105,23 @@ export const DataProvider = ({ children }: { children: React.ReactNode }) => {
102105 const buildTree = useCallback (
103106 ( doc : Document , store : Record < string , TreeDocument > , keyPath : string [ ] = [ ] ) : TreeDocument => {
104107 const selfKey = getStoreKey ( doc ) ;
105- keyPath . push ( selfKey ) ;
108+ const nextPath = [ ... keyPath , selfKey ] ;
106109 const storedDoc = structuredClone ( store [ selfKey ] ?? doc ) ;
107110 const initialLinks = storedDoc . links || [ ] ;
108111 let creLinks = initialLinks . filter (
109- ( x ) => ! ! x . document && ! keyPath . includes ( getStoreKey ( x . document ) ) && getStoreKey ( x . document ) in store
112+ ( x ) => ! ! x . document && ! nextPath . includes ( getStoreKey ( x . document ) ) && getStoreKey ( x . document ) in store
110113 ) ;
111114 creLinks = creLinks . filter ( ( x ) => x . ltype === 'Contains' ) ;
112115 creLinks = creLinks . map ( ( x ) => ( {
113116 ltype : x . ltype ,
114- document : buildTree ( x . document , store , keyPath ) ,
117+ document : buildTree ( x . document , store , nextPath ) ,
115118 } ) ) ;
116119 storedDoc . links = [ ...creLinks ] ;
117120 const standards = initialLinks . filter (
118121 ( link ) =>
119122 link . document &&
120123 link . document . doctype === 'Standard' &&
121- ! keyPath . includes ( getStoreKey ( link . document ) )
124+ ! nextPath . includes ( getStoreKey ( link . document ) )
122125 ) ;
123126 storedDoc . links = [ ...creLinks , ...standards ] ;
124127 return storedDoc ;
@@ -154,51 +157,68 @@ export const DataProvider = ({ children }: { children: React.ReactNode }) => {
154157 setDataTree ( [ ] ) ;
155158 return [ ] ;
156159 }
157- const result = await axios . get ( `${ apiUrl } /root_cres` ) ;
158- const treeData = result . data . data . map ( ( x : Document ) => buildTree ( x , store ) ) ;
159- setDataTree ( treeData ) ;
160- return treeData ;
160+ try {
161+ const result = await axios . get ( `${ apiUrl } /root_cres` ) ;
162+ const treeData = result . data . data . map ( ( x : Document ) => buildTree ( x , store ) ) ;
163+ setDataTree ( treeData ) ;
164+ return treeData ;
165+ } catch ( err ) {
166+ const error = err instanceof Error ? err : new Error ( 'Failed to load explorer tree' ) ;
167+ setDataLoadError ( error ) ;
168+ throw error ;
169+ }
161170 } ,
162171 [ apiUrl , buildTree ]
163172 ) ;
164173
165174 const loadPage = useCallback (
166175 async ( page : number ) : Promise < Record < string , TreeDocument > > => {
167- if ( loadingPageRef . current ) {
168- return dataStoreRef . current ;
169- }
170- loadingPageRef . current = true ;
171- setIsLoadingMore ( true ) ;
172- try {
173- const result = await axios . get ( `${ apiUrl } /all_cres` , {
174- params : { page, per_page : EXPLORER_CRE_PAGE_SIZE } ,
175- } ) ;
176- const docs : Document [ ] = result . data . data || [ ] ;
177- const pagesTotal = Number ( result . data . total_pages ) || 1 ;
178- const nextStore = mergeDocsIntoStore ( docs , dataStoreRef . current , getStoreKey ) ;
179- const pagesLoaded = page ;
180-
181- dataStoreRef . current = nextStore ;
182- setDataStore ( nextStore ) ;
183- setLoadedPages ( pagesLoaded ) ;
184- setTotalPages ( pagesTotal ) ;
185- loadedPagesRef . current = pagesLoaded ;
186- totalPagesRef . current = pagesTotal ;
187-
188- const fullLoaded = pagesLoaded >= pagesTotal ;
189- if ( fullLoaded ) {
190- isFullStoreLoadedRef . current = true ;
191- setIsFullStoreLoaded ( true ) ;
176+ const nextLoad = loadChainRef . current . then ( async ( ) => {
177+ if ( isFullStoreLoadedRef . current || ( loadedPagesRef . current >= page && loadedPagesRef . current > 0 ) ) {
178+ return dataStoreRef . current ;
192179 }
193180
194- const treeData = await rebuildDataTree ( nextStore ) ;
195- await persistCache ( nextStore , pagesLoaded , pagesTotal , fullLoaded , treeData ) ;
181+ loadingPageRef . current = true ;
182+ setIsLoadingMore ( true ) ;
183+ try {
184+ const result = await axios . get ( `${ apiUrl } /all_cres` , {
185+ params : { page, per_page : EXPLORER_CRE_PAGE_SIZE } ,
186+ } ) ;
187+ const docs : Document [ ] = result . data . data || [ ] ;
188+ const pagesTotal = Number ( result . data . total_pages ) || 1 ;
189+ const nextStore = mergeDocsIntoStore ( docs , dataStoreRef . current , getStoreKey ) ;
190+ const pagesLoaded = page ;
191+
192+ dataStoreRef . current = nextStore ;
193+ setDataStore ( nextStore ) ;
194+ setLoadedPages ( pagesLoaded ) ;
195+ setTotalPages ( pagesTotal ) ;
196+ loadedPagesRef . current = pagesLoaded ;
197+ totalPagesRef . current = pagesTotal ;
198+ setDataLoadError ( null ) ;
199+
200+ const fullLoaded = pagesLoaded >= pagesTotal ;
201+ if ( fullLoaded ) {
202+ isFullStoreLoadedRef . current = true ;
203+ setIsFullStoreLoaded ( true ) ;
204+ }
205+
206+ const treeData = await rebuildDataTree ( nextStore ) ;
207+ await persistCache ( nextStore , pagesLoaded , pagesTotal , fullLoaded , treeData ) ;
208+
209+ return nextStore ;
210+ } catch ( err ) {
211+ const error = err instanceof Error ? err : new Error ( 'Failed to load CRE data' ) ;
212+ setDataLoadError ( error ) ;
213+ throw error ;
214+ } finally {
215+ loadingPageRef . current = false ;
216+ setIsLoadingMore ( false ) ;
217+ }
218+ } ) ;
196219
197- return nextStore ;
198- } finally {
199- loadingPageRef . current = false ;
200- setIsLoadingMore ( false ) ;
201- }
220+ loadChainRef . current = nextLoad . catch ( ( ) => dataStoreRef . current ) ;
221+ return nextLoad ;
202222 } ,
203223 [ apiUrl , getStoreKey , persistCache , rebuildDataTree ]
204224 ) ;
@@ -289,6 +309,8 @@ export const DataProvider = ({ children }: { children: React.ReactNode }) => {
289309 } else if ( Object . keys ( dataStoreRef . current ) . length ) {
290310 await rebuildDataTree ( dataStoreRef . current ) ;
291311 }
312+ } catch {
313+ // loadPage/rebuildDataTree already set dataLoadError
292314 } finally {
293315 setDataLoading ( false ) ;
294316 }
@@ -332,6 +354,7 @@ export const DataProvider = ({ children }: { children: React.ReactNode }) => {
332354 hasMore,
333355 isLoadingMore,
334356 fullLoadProgress,
357+ dataLoadError,
335358 loadNextPage,
336359 ensureFullExplorerData,
337360 } }
0 commit comments