src/blackrock/useMarketFeed.ts

import {useCallback,useEffect,useRef,useState} from 'react';
import {api} from './api';
import {appendMarketPage,emptyMarketPages,type MarketPage} from './market-board-model';
/** Mount with a key for the query/sort/window. Pausing for item details keeps rows and scroll. */
export function useMarketFeed(path:string,active:boolean,automatic:boolean,atTop:()=>boolean){
 const [state,setState]=useState(()=>({...emptyMarketPages(),loading:false,error:null as string|null}));
 const current=useRef(state);current.current=state;
 const failed=useRef<{offset:number;replace:boolean}|null>(null);
 const alive=useRef(true),busy=useRef(false),controller=useRef<AbortController|null>(null),enabled=useRef(active);enabled.current=active;
 useEffect(()=>{alive.current=true;return()=>{alive.current=false;controller.current?.abort();}},[]);
 const request=useCallback(async(offset:number,replace=false)=>{
  if(busy.current||!enabled.current)return false;
  busy.current=true;failed.current=null;controller.current=new AbortController();setState(s=>({...s,loading:true,error:null}));
  try{
   const page=await api<MarketPage>(`${path}&offset=${offset}`,{signal:controller.current.signal});
   if(!alive.current)return false;
   const next=appendMarketPage(replace?emptyMarketPages():current.current,page,offset);
   const value={...next,loading:false,error:null};current.current=value;setState(value);return true;
  }catch(error){failed.current={offset,replace};if(alive.current)setState(s=>({...s,loading:false,error:(error as Error).message}));return false;}
  finally{busy.current=false;}
 },[path]);
 useEffect(()=>{if(active&&!current.current.pages.length&&!current.current.error)void request(0,true);},[active,request]);
 useEffect(()=>{
  if(!active||!automatic)return;
  const timer=setInterval(()=>{if(current.current.pages.length<=1&&atTop()&&document.visibilityState==='visible')void request(0,true);},60000);
  return()=>clearInterval(timer);
 },[active,automatic,request,atTop]);
 return {...state,loadMore:()=>{const next=current.current.nextOffset;if(next!==null)void request(next);},retry:()=>{const last=failed.current;if(last)void request(last.offset,last.replace);},refresh:()=>request(0,true)};
}