import React , { ComponentType , createContext , DependencyList , forwardRef , ReactNode , useContext , useEffect , useLayoutEffect , useMemo , useReducer , } from "react" ; import qs from "querystring" ; import { canUseDOM , DeepReadonly , isDevelopment } from "./utils" ; import { RouteDefinition , ParamsOfRoute , StateOfRoute } from "./RouteDefiner" ; import { RouterContext , BeforeRouteListener } from "./RouterContext" ; import { RouterEvents } from "./RouterEvents" ; import { Location } from "history" ; import { buildPath , BuildPath } from "./builder" ; const useIsomorphicLayoutEffect = canUseDOM ( ) ? useLayoutEffect : useEffect ; const Context = createContext < RouterContext | null > ( null ) ; Context . displayName = "FrouteContext" ; const checkExpectedRoute = ( router , expectRoute ? , methodName ? ) => { if ( expectRoute && router . getCurrentMatch ( ) ?. route !== expectRoute ) { console . warn ( `Froute: Expected route and current route not matched in \`${ methodName }\`` ) ; } } ; export const FrouteContext = ( { router , children , } ) => { return < Context . Provider value = { router } > { children } < /Context.Provider>; } ; /**
 * Do not expose to public API. It's Froute internal only hooks.
 * WHY: Protect direct router operating from Components.
 * If allow it, Router status can changed by anywhere and becomes unpredictable.
 */ export const useRouterContext = ( ) => { const router = useContext ( Context ) ; if ( ! router ) { throw new Error ( "FrouteContext must be placed of top of useRouter" ) ; } return router ; } ; export const useRouteComponent = ( ) => { const router = useRouterContext ( ) ; const match = router . getCurrentMatch ( ) ; const PageComponent = match ?. route . getActor ( ) ?. cachedComponent ; const [ , rerender ] = useReducer ( ( s ) => s + 1 , 0 ) ; useIsomorphicLayoutEffect ( ( ) => { router . observeRouteChanged ( rerender ) ; return ( ) => router . unobserveRouteChanged ( rerender ) ; } , [ router , rerender ] ) ; return useMemo ( ( ) => ( { PageComponent } ) , [ match ] ) ; } ; export interface UseRouter { < R extends RouteDefinition < any , any > = any > ( ) : NextCompatRouter < R > ; } interface NextCompatRouter < R extends RouteDefinition < any , any > > { pathname : string ; query : ParamsOfRoute < R > & { [ key ] : string | string [ ] ; } ; push : ( url : string ) => void ; replace : ( url : string ) => void ; prefetch : ( url : string ) => void ; back : FrouteNavigator [ "back" ] ; reload : ( ) => void ; events : RouterEvents ; } /**
 * Next.js subset-compat router
 *
 * - URL Object is not supported in `push`, `replace` currentry
 * - `as` is not supported currentry
 * - `beforePopState` is not supported
 * - router.events
 */ export const useRouter = ( ) => { const router = useRouterContext ( ) ; const location = router . getCurrentLocation ( ) ; const match = router . getCurrentMatch ( ) ; const nav = useNavigation ( ) ; return useMemo ( ( ) => ( { pathname : location . pathname , query : { ... ( match ?. match . query as any ) , ... ( match ?. match . params as any ) , } , push : ( url ) => nav . push ( url ) , replace : ( url ) => nav . replace ( url ) , prefetch : ( url ) => { const match = router . resolveRoute ( url ) ; if ( ! match ) return ; router . preloadRoute ( match , { onlyComponentPreload : true , } ) ; } , back : nav . back , reload : ( ) => window . location . reload ( ) , events : router . events , } ) , [ location , match ] ) ; } ; export type RouterProps < R extends RouteDefinition < any , any > = any > = { router : NextCompatRouter < R > ; } ; export const withRouter = < P extends RouterProps > ( Component ) : ComponentType < Omit < P , "router" > > => { const WithRouter = forwardRef < any , any > ( ( props , ref ) => { const router = useRouter ( ) ; return < Component { ... props } ref = { ref } router = { router } / > ; } ) ; WithRouter . displayName = `WithRouter(${ Component . displayName ?? ( Component as any ) . name })` ; return WithRouter ; } ; export interface UseFrouteRouter { < R extends RouteDefinition < any , any > = any > ( r ? ) : FrouteRouter < R > ; } interface FrouteRouter < R extends RouteDefinition < any , any > > extends NextCompatRouter < R > { searchQuery : Record < string , string | string [ ] | undefined > ; location : DeepReadonly < Location < StateOfRoute < R > > > ; buildPath : BuildPath ; historyState : { get : ( ) => StateOfRoute < R > ; set : ( state : StateOfRoute < R > ) => void ; } ; } export const useFrouteRouter = < R extends RouteDefinition < any , any > > ( r ? ) : FrouteRouter < R > => { const router = useRouterContext ( ) ; const nextCompatRouter = useRouter < R > ( ) ; const location = router . getCurrentLocation ( ) ; if ( isDevelopment ) { checkExpectedRoute ( router , r , "useLocation" ) ; } return useMemo ( ( ) => ( { ... nextCompatRouter , searchQuery : qs . parse ( location . search . slice ( 1 ) ?? "" ) , location : { ... location , state : location . state . app } , buildPath , historyState : { get : router . getHistoryState , set : router . setHistoryState , } , } ) , [ location , nextCompatRouter . pathname , nextCompatRouter . query ] ) ; } ; deprecated export const useLocation = < R extends RouteDefinition < any , any > > ( expectRoute ? ) => { const router = useRouterContext ( ) ; const location = router . getCurrentLocation ( ) ; if ( isDevelopment ) { checkExpectedRoute ( router , expectRoute , "useLocation" ) ; } return useMemo ( ( ) => ( { key : location . key , pathname : location . pathname , search : location . search , query : qs . parse ( location . search . slice ( 1 ) ?? "" ) , hash : location . hash , state : location . state . app as StateOfRoute < R > , } ) , [ location . pathname , location . search , location . search ] ) ; } ; deprecated export const useHistoryState = < R extends RouteDefinition < any , any > = RouteDefinition < any , any > > ( expectRoute ? ) : [ getHistoryState : ( ) => DeepReadonly < StateOfRoute < R > > , setHistoryState : ( state : StateOfRoute < R > ) => void ] => { const router = useRouterContext ( ) ; if ( isDevelopment ) { checkExpectedRoute ( router , expectRoute , "useHistoryState" ) ; } return useMemo ( ( ) => [ router . getHistoryState , router . setHistoryState ] , [ ] ) ; } ; interface UseParams { ( ) : { [ param ] : string | undefined ; } ; < T extends RouteDefinition < any , any > > ( expectRoute ? ) : ParamsOfRoute < T > ; } deprecated export const useParams = < T extends RouteDefinition < any , any > = RouteDefinition < any , any > > ( expectRoute ? ) => { const router = useRouterContext ( ) ; const location = router . getCurrentLocation ( ) ; const match = location ? router . resolveRoute ( location . pathname ) : null ; if ( isDevelopment ) { checkExpectedRoute ( router , expectRoute , "useParams" ) ; } return match ? ( match . match . params as ParamsOfRoute < T > ) : { } ; } ; deprecated export interface FrouteNavigator { push < R extends RouteDefinition < any , any > > ( route , params , extra ? ) : void ; push ( pathname ) : void ; replace < R extends RouteDefinition < any , any > > ( route , params , extra ? ) : void ; replace ( pathname ) : void ; back ( ) : void ; forward ( ) : void ; } deprecated export const useNavigation = ( ) => { const router = useRouterContext ( ) ; const { buildPath } = useUrlBuilder ( ) ; return useMemo < FrouteNavigator > ( ( ) => ( { push : < R extends RouteDefinition < any , any > > ( route , params = { } as any , { query , hash = "" , state , } = { } ) => { const pathname = typeof route === "string" ? route : buildPath ( route , params , query ) ; const resolvedRoute = typeof route !== "string" ? route : router . resolveRoute ( pathname + hash ) ?. route ; if ( ! resolvedRoute ) return ; router . navigate ( pathname + hash , { state , action : "PUSH" , } ) ; } , replace : < R extends RouteDefinition < any , any > > ( route , params = { } as any , { query , hash = "" , state , } = { } ) => { const pathname = typeof route === "string" ? route : buildPath ( route , params , query ) ; router . navigate ( pathname + hash , { state , action : "REPLACE" , } ) ; } , back : ( ) => router . history . back ( ) , forward : ( ) => router . history . forward ( ) , } ) , [ router , router . history ] ) ; } ; deprecated export const useUrlBuilder = ( ) => { const router = useRouterContext ( ) ; return useMemo ( ( ) => ( { buildPath : router . buildPath , } ) , [ router ] ) ; } ; /** Handling route change */ export const useBeforeRouteChange = ( /** Return Promise&lt;false&gt; | false to prevent route changing. This listener only one can be set at a time */ beforeRouteListener , deps ) => { const router = useRouterContext ( ) ; useEffect ( ( ) => { router . setBeforeRouteChangeListener ( beforeRouteListener ) ; return ( ) => router . clearBeforeRouteChangeListener ( ) ; } , deps ) ; } ;