import type { Action , Location } from "history" ; import type { FocusEventHandler , FormHTMLAttributes , MouseEventHandler , TouchEventHandler } from "react" ; import React from "react" ; import type { Navigator } from "react-router" ; import { Router , Link as RouterLink , NavLink as RouterNavLink , useLocation , useRoutes , useNavigate , useHref , useResolvedPath } from "react-router-dom" ; import type { LinkProps , NavLinkProps } from "react-router-dom" ; import type { AppData } from "./data" ; import type { FormEncType , FormMethod } from "./data" ; import type { EntryContext , AssetsManifest } from "./entry" ; import type { ComponentDidCatchEmulator , SerializedError } from "./errors" ; import { RemixRootDefaultErrorBoundary , RemixErrorBoundary , RemixRootDefaultCatchBoundary , RemixCatchBoundary } from "./errorBoundaries" ; import invariant from "./invariant" ; import { getDataLinkHrefs , getModuleLinkHrefs , getNewMatchesForLinks , getStylesheetPrefetchLinks } from "./links" ; import type { HtmlLinkDescriptor , PrefetchPageDescriptor } from "./links" ; import { getLinksForMatches , isPageLinkDescriptor } from "./links" ; import { createHtml } from "./markup" ; import type { ClientRoute } from "./routes" ; import { createClientRoutes } from "./routes" ; import type { RouteData } from "./routeData" ; import type { RouteMatch } from "./routeMatching" ; import { matchClientRoutes } from "./routeMatching" ; import type { RouteModules , HtmlMetaDescriptor } from "./routeModules" ; import { createTransitionManager } from "./transition" ; import type { Transition , Fetcher , Submission } from "./transition" ; export { ScrollRestoration } from "./scroll-restoration" ; interface RemixEntryContextType { manifest : AssetsManifest ; matches : RouteMatch < ClientRoute > [ ] ; routeData : { [ routeId ] : RouteData ; } ; actionData ? : RouteData ; pendingLocation ? : Location ; componentDidCatchEmulator : ComponentDidCatchEmulator ; routeModules : RouteModules ; serverHandoffString ? : string ; clientRoutes : ClientRoute [ ] ; transitionManager : ReturnType < typeof createTransitionManager > ; } const RemixEntryContext = React . createContext < RemixEntryContextType | undefined > ( undefined ) ; function useRemixEntryContext ( ) { let context = React . useContext ( RemixEntryContext ) ; invariant ( context , "You must render this element inside a <Remix> element" ) ; return context ; } export function RemixEntry ( { context : entryContext , action , location : historyLocation , navigator : _navigator , static : staticProp = false } ) { let { manifest , routeData : documentLoaderData , actionData : documentActionData , routeModules , serverHandoffString , componentDidCatchEmulator : entryComponentDidCatchEmulator } = entryContext ; let clientRoutes = React . useMemo ( ( ) => createClientRoutes ( manifest . routes , routeModules , RemixRoute ) , [ manifest , routeModules ] ) ; let [ clientState , setClientState ] = React . useState ( entryComponentDidCatchEmulator ) ; let [ transitionManager ] = React . useState ( ( ) => { return createTransitionManager ( { routes : clientRoutes , actionData : documentActionData , loaderData : documentLoaderData , location : historyLocation , catch : entryComponentDidCatchEmulator . catch , catchBoundaryId : entryComponentDidCatchEmulator . catchBoundaryRouteId , onRedirect : _navigator . replace , onChange : state => { setClientState ( { catch : state . catch , error : state . error , catchBoundaryRouteId : state . catchBoundaryId , loaderBoundaryRouteId : state . errorBoundaryId , renderBoundaryRouteId : null , trackBoundaries : false , trackCatchBoundaries : false } ) ; } } ) ; } ) ; let navigator = React . useMemo ( ( ) => { let push = ( to , state ) => { return transitionManager . getState ( ) . transition . state !== "idle" ? _navigator . replace ( to , state ) : _navigator . push ( to , state ) ; } ; return { ... _navigator , push } ; } , [ _navigator , transitionManager ] ) ; let { location , matches , loaderData , actionData } = transitionManager . getState ( ) ; React . useEffect ( ( ) => { let { location } = transitionManager . getState ( ) ; if ( historyLocation === location ) return ; transitionManager . send ( { type : "navigation" , location : historyLocation , submission : consumeNextNavigationSubmission ( ) , action } ) ; } , [ transitionManager , historyLocation , action ] ) ; let ssrErrorBeforeRoutesRendered = clientState . error && clientState . renderBoundaryRouteId === null && clientState . loaderBoundaryRouteId === null ? deserializeError ( clientState . error ) : undefined ; let ssrCatchBeforeRoutesRendered = clientState . catch && clientState . catchBoundaryRouteId === null ? clientState . catch : undefined ; return ( < RemixEntryContext . Provider value = { { matches , manifest , componentDidCatchEmulator : clientState , routeModules , serverHandoffString , clientRoutes , routeData : loaderData , actionData , transitionManager } } > < RemixErrorBoundary location = { location } component = { RemixRootDefaultErrorBoundary } error = { ssrErrorBeforeRoutesRendered } > < RemixCatchBoundary location = { location } component = { RemixRootDefaultCatchBoundary } catch = { ssrCatchBeforeRoutesRendered } > < Router navigationType = { action } location = { location } navigator = { navigator } static = { staticProp } > < Routes /> < /Router> < /RemixCatchBoundary> < /RemixErrorBoundary> < /RemixEntryContext.Provider>); } function deserializeError ( data ) { let error = new Error ( data . message ) ; error . stack = data . stack ; return error ; } function Routes ( ) { let { clientRoutes } = useRemixEntryContext ( ) ; let element = useRoutes ( clientRoutes ) || ( clientRoutes [ 0 ] . element as any ) ; return element ; } interface RemixRouteContextType { data : AppData ; id : string ; } const RemixRouteContext = React . createContext < RemixRouteContextType | undefined > ( undefined ) ; function useRemixRouteContext ( ) { let context = React . useContext ( RemixRouteContext ) ; invariant ( context , "You must render this element in a remix route element" ) ; return context ; } function DefaultRouteComponent ( { id } ) { throw new Error ( `Route "${ id }" has no component! Please go add a \`default\` export in the route module file.\n` + "If you were trying to navigate or submit to a resource route, use `<a>` instead of `<Link>` or `<Form reloadDocument>`." ) ; } export function RemixRoute ( { id } ) { let location = useLocation ( ) ; let { routeData , routeModules , componentDidCatchEmulator } = useRemixEntryContext ( ) ; let data = routeData [ id ] ; let { default : Component , CatchBoundary , ErrorBoundary } = routeModules [ id ] ; let element = Component ? < Component /> : <DefaultRouteComponent id={id}/ > ; let context = { data , id } ; if ( CatchBoundary ) { let maybeServerCaught = componentDidCatchEmulator . catch && componentDidCatchEmulator . catchBoundaryRouteId === id ? componentDidCatchEmulator . catch : undefined ; if ( componentDidCatchEmulator . trackCatchBoundaries ) { componentDidCatchEmulator . catchBoundaryRouteId = id ; } context = maybeServerCaught ? { id , get data ( ) { console . error ( "You cannot `useLoaderData` in a catch boundary." ) ; return undefined ; } } : { id , data } ; element = ( < RemixCatchBoundary location = { location } component = { CatchBoundary } catch = { maybeServerCaught } > { element } < /RemixCatchBoundary>); } if ( ErrorBoundary ) { let maybeServerRenderError = componentDidCatchEmulator . error && ( componentDidCatchEmulator . renderBoundaryRouteId === id || componentDidCatchEmulator . loaderBoundaryRouteId === id ) ? deserializeError ( componentDidCatchEmulator . error ) : undefined ; if ( componentDidCatchEmulator . trackBoundaries ) { componentDidCatchEmulator . renderBoundaryRouteId = id ; } context = maybeServerRenderError ? { id , get data ( ) { console . error ( "You cannot `useLoaderData` in an error boundary." ) ; return undefined ; } } : { id , data } ; element = ( < RemixErrorBoundary location = { location } component = { ErrorBoundary } error = { maybeServerRenderError } > { element } < /RemixErrorBoundary>); } return ( < RemixRouteContext . Provider value = { context } > { element } < /RemixRouteContext.Provider>); } /**
 * Defines the prefetching behavior of the link:
 *
 * - "intent": Default, fetched when the user focuses or hovers the link
 * - "render": Fetched when the link is rendered
 * - "none": Never fetched
 */ type PrefetchBehavior = "intent" | "render" | "none" ; export interface RemixLinkProps extends LinkProps { prefetch ? : PrefetchBehavior ; } export interface RemixNavLinkProps extends NavLinkProps { prefetch ? : PrefetchBehavior ; } interface PrefetchHandlers { onFocus ? : FocusEventHandler < Element > ; onBlur ? : FocusEventHandler < Element > ; onMouseEnter ? : MouseEventHandler < Element > ; onMouseLeave ? : MouseEventHandler < Element > ; onTouchStart ? : TouchEventHandler < Element > ; } function usePrefetchBehavior ( prefetch , theirElementProps ) { let [ maybePrefetch , setMaybePrefetch ] = React . useState ( false ) ; let [ shouldPrefetch , setShouldPrefetch ] = React . useState ( false ) ; let { onFocus , onBlur , onMouseEnter , onMouseLeave , onTouchStart } = theirElementProps ; React . useEffect ( ( ) => { if ( prefetch === "render" ) { setShouldPrefetch ( true ) ; } } , [ prefetch ] ) ; let setIntent = ( ) => { if ( prefetch === "intent" ) { setMaybePrefetch ( true ) ; } } ; let cancelIntent = ( ) => { if ( prefetch === "intent" ) { setMaybePrefetch ( false ) ; } } ; React . useEffect ( ( ) => { if ( maybePrefetch ) { let id = setTimeout ( ( ) => { setShouldPrefetch ( true ) ; } , 100 ) ; return ( ) => { clearTimeout ( id ) ; } ; } } , [ maybePrefetch ] ) ; return [ shouldPrefetch , { onFocus : composeEventHandlers ( onFocus , setIntent ) , onBlur : composeEventHandlers ( onBlur , cancelIntent ) , onMouseEnter : composeEventHandlers ( onMouseEnter , setIntent ) , onMouseLeave : composeEventHandlers ( onMouseLeave , cancelIntent ) , onTouchStart : composeEventHandlers ( onTouchStart , setIntent ) } ] ; } export let NavLink = React . forwardRef < HTMLAnchorElement , RemixNavLinkProps > ( ( { to , prefetch = "none" , ... props } , forwardedRef ) => { let href = useHref ( to ) ; let [ shouldPrefetch , prefetchHandlers ] = usePrefetchBehavior ( prefetch , props ) ; return ( < > < RouterNavLink ref = { forwardedRef } to = { to } { ... prefetchHandlers } { ... props } /> { shouldPrefetch && < PrefetchPageLinks page = { href } />} < />); } ) ; export let Link = React . forwardRef < HTMLAnchorElement , RemixLinkProps > ( ( { to , prefetch = "none" , ... props } , forwardedRef ) => { let href = useHref ( to ) ; let [ shouldPrefetch , prefetchHandlers ] = usePrefetchBehavior ( prefetch , props ) ; return ( < > < RouterLink ref = { forwardedRef } to = { to } { ... prefetchHandlers } { ... props } /> { shouldPrefetch && < PrefetchPageLinks page = { href } />} < />); } ) ; export function composeEventHandlers < EventType extends React . SyntheticEvent | Event > ( theirHandler , ourHandler ) { return event => { theirHandler && theirHandler ( event ) ; if ( ! event . defaultPrevented ) { ourHandler ( event ) ; } } ; } /**
 * Renders the `<link>` tags for the current routes.
 */ export function Links ( ) { let { matches , routeModules , manifest } = useRemixEntryContext ( ) ; let links = React . useMemo ( ( ) => getLinksForMatches ( matches , routeModules , manifest ) , [ matches , routeModules , manifest ] ) ; return ( < > { links . map ( link => isPageLinkDescriptor ( link ) ? ( < PrefetchPageLinks key = { link . page } { ... link } / > ) : ( < link key = { link . rel + link . href } { ... link } / > ) ) } < />); } export function PrefetchPageLinks ( { page , ... dataLinkProps } ) { let { clientRoutes } = useRemixEntryContext ( ) ; let matches = React . useMemo ( ( ) => matchClientRoutes ( clientRoutes , page ) , [ clientRoutes , page ] ) ; if ( ! matches ) { console . warn ( `Tried to prefetch ${ page } but no routes matched.` ) ; return null ; } return ( < PrefetchPageLinksImpl page = { page } matches = { matches } { ... dataLinkProps } />); } function usePrefetchedStylesheets ( matches ) { let { routeModules } = useRemixEntryContext ( ) ; let [ styleLinks , setStyleLinks ] = React . useState < HtmlLinkDescriptor [ ] > ( [ ] ) ; React . useEffect ( ( ) => { let interrupted = false ; getStylesheetPrefetchLinks ( matches , routeModules ) . then ( links => { if ( ! interrupted ) setStyleLinks ( links ) ; } ) ; return ( ) => { interrupted = true ; } ; } , [ matches , routeModules ] ) ; return styleLinks ; } function PrefetchPageLinksImpl ( { page , matches : nextMatches , ... linkProps } ) { let location = useLocation ( ) ; let { matches , manifest } = useRemixEntryContext ( ) ; let newMatchesForData = React . useMemo ( ( ) => getNewMatchesForLinks ( page , nextMatches , matches , location , "data" ) , [ page , nextMatches , matches , location ] ) ; let newMatchesForAssets = React . useMemo ( ( ) => getNewMatchesForLinks ( page , nextMatches , matches , location , "assets" ) , [ page , nextMatches , matches , location ] ) ; let dataHrefs = React . useMemo ( ( ) => getDataLinkHrefs ( page , newMatchesForData , manifest ) , [ newMatchesForData , page , manifest ] ) ; let moduleHrefs = React . useMemo ( ( ) => getModuleLinkHrefs ( newMatchesForAssets , manifest ) , [ newMatchesForAssets , manifest ] ) ; let styleLinks = usePrefetchedStylesheets ( newMatchesForAssets ) ; return ( < > { dataHrefs . map ( href => ( < link key = { href } rel = "prefetch" as = "fetch" href = { href } { ... linkProps } / > ) ) } { moduleHrefs . map ( href => ( < link key = { href } rel = "modulepreload" href = { href } { ... linkProps } / > ) ) } { styleLinks . map ( link => ( < link key = { link . href } { ... link } / > ) ) } < />); } /**
 * Renders the `<title>` and `<meta>` tags for the current routes.
 */ export function Meta ( ) { let { matches , routeData , routeModules } = useRemixEntryContext ( ) ; let location = useLocation ( ) ; let meta = { } ; let parentsData = { } ; for ( let match of matches ) { let routeId = match . route . id ; let data = routeData [ routeId ] ; let params = match . params ; let routeModule = routeModules [ routeId ] ; if ( routeModule . meta ) { let routeMeta = typeof routeModule . meta === "function" ? routeModule . meta ( { data , parentsData , params , location } ) : routeModule . meta ; Object . assign ( meta , routeMeta ) ; } parentsData [ routeId ] = data ; } return ( < > { Object . keys ( meta ) . map ( name => { let value = meta [ name ] ; let isOpenGraphTag = name . startsWith ( "og:" ) ; return name === "title" ? ( < title key = "title" > { meta [ name ] } < /title>) : Array.isArray(value) ? (value.map(content => isOpenGraphTag ? (<meta key={name + content} property={name} content={content}/ > ) : ( < meta key = { name + content } name = { name } content = { content } / > ) ) ) : isOpenGraphTag ? ( < meta key = { name } property = { name } content = { value } / > ) : ( < meta key = { name } name = { name } content = { value } / > ) ; } ) } < />); } type ScriptProps = Omit < React . HTMLProps < HTMLScriptElement > , "children" | "async" | "defer" | "src" | "type" | "noModule" | "dangerouslySetInnerHTML" | "suppressHydrationWarning" > ; param props export function Scripts ( props ) { let { manifest , matches , pendingLocation , clientRoutes , serverHandoffString } = useRemixEntryContext ( ) ; let initialScripts = React . useMemo ( ( ) => { let contextScript = serverHandoffString ? `window.__remixContext = ${ serverHandoffString };` : "" ; let routeModulesScript = `${ matches . map ( ( match , index ) => `import * as route${ index } from ${ JSON . stringify ( manifest . routes [ match . route . id ] . module ) };` ) . join ( "\n" ) }
window.__remixRouteModules = {${ matches . map ( ( match , index ) => `${ JSON . stringify ( match . route . id ) }:route${ index }` ) . join ( "," ) }};` ; return ( < > < script { ... props } suppressHydrationWarning dangerouslySetInnerHTML = { createHtml ( contextScript ) } / > < script { ... props } src = { manifest . url } / > < script { ... props } dangerouslySetInnerHTML = { createHtml ( routeModulesScript ) } type = "module" / > < script { ... props } src = { manifest . entry . module } type = "module" / > < />); } , [ ] ) ; let nextMatches = React . useMemo ( ( ) => { if ( pendingLocation ) { let matches = matchClientRoutes ( clientRoutes , pendingLocation ) ; invariant ( matches , `No routes match path "${ pendingLocation . pathname }"` ) ; return matches ; } return [ ] ; } , [ pendingLocation , clientRoutes ] ) ; let routePreloads = matches . concat ( nextMatches ) . map ( match => { let route = manifest . routes [ match . route . id ] ; return ( route . imports || [ ] ) . concat ( [ route . module ] ) ; } ) . flat ( 1 ) ; let preloads = manifest . entry . imports . concat ( routePreloads ) ; return ( < > { dedupe ( preloads ) . map ( path => ( < link key = { path } rel = "modulepreload" href = { path } crossOrigin = { props . crossOrigin } / > ) ) } { initialScripts } < />); } function dedupe ( array ) { return [ ... new Set ( array ) ] ; } export interface FormProps extends FormHTMLAttributes < HTMLFormElement > { /**
     * The HTTP verb to use when the form is submit. Supports "get", "post",
     * "put", "delete", "patch".
     *
     * Note: If JavaScript is disabled, you'll need to implement your own "method
     * override" to support more than just GET and POST.
     */ method ? : FormMethod ; /**
     * Normal `<form action>` but supports React Router's relative paths.
     */ action ? : string ; /**
     * Normal `<form encType>`.
     *
     * Note: Remix only supports `application/x-www-form-urlencoded` right now
     * but will soon support `multipart/form-data` as well.
     */ encType ? : FormEncType ; /**
     * Forces a full document navigation instead of a fetch.
     */ reloadDocument ? : boolean ; /**
     * Replaces the current entry in the browser history stack when the form
     * navigates. Use this if you don't want the user to be able to click "back"
     * to the page with the form on it.
     */ replace ? : boolean ; /**
     * A function to call when the form is submitted. If you call
     * `event.preventDefault()` then this form will not do anything.
     */ onSubmit ? : React . FormEventHandler < HTMLFormElement > ; } /**
 * A Remix-aware `<form>`. It behaves like a normal form except that the
 * interaction with the server is with `fetch` instead of new document
 * requests, allowing components to add nicer UX to the page as the form is
 * submitted and returns with data.
 */ export let Form = React . forwardRef < HTMLFormElement , FormProps > ( ( props , ref ) => { return < FormImpl { ... props } ref = { ref } / > ; } ) ; interface FormImplProps extends FormProps { fetchKey ? : string ; } export let FormImpl = React . forwardRef < HTMLFormElement , FormImplProps > ( ( { reloadDocument = false , replace = false , method = "get" , action = "." , encType = "application/x-www-form-urlencoded" , fetchKey , onSubmit , ... props } , forwardedRef ) => { let submit = useSubmitImpl ( fetchKey ) ; let formMethod = method . toLowerCase ( ) === "get" ? "get" : "post" ; let formAction = useFormAction ( action , formMethod ) ; let formRef = React . useRef < HTMLFormElement > ( ) ; let ref = useComposedRefs ( forwardedRef , formRef ) ; let clickedButtonRef = React . useRef < any > ( ) ; React . useEffect ( ( ) => { let form = formRef . current ; if ( ! form ) return ; function handleClick ( event ) { if ( ! ( event . target instanceof HTMLElement ) ) return ; let submitButton = event . target . closest < HTMLButtonElement | HTMLInputElement > ( "button,input[type=submit]" ) ; if ( submitButton && submitButton . type === "submit" ) { clickedButtonRef . current = submitButton ; } } form . addEventListener ( "click" , handleClick ) ; return ( ) => { form && form . removeEventListener ( "click" , handleClick ) ; } ; } , [ ] ) ; return ( < form ref = { ref } method = { formMethod } action = { formAction } encType = { encType } onSubmit = { reloadDocument ? undefined : event => { onSubmit && onSubmit ( event ) ; if ( event . defaultPrevented ) return ; event . preventDefault ( ) ; submit ( clickedButtonRef . current || event . currentTarget , { method , replace } ) ; clickedButtonRef . current = null ; } } { ... props } />); } ) ; function isActionRequestMethod ( method ) { method = method . toLowerCase ( ) ; return ( method === "post" || method === "put" || method === "patch" || method === "delete" ) ; } /**
 * Resolves a `<form action>` path relative to the current route.
 */ export function useFormAction ( action = "." , method = "get" ) { let { id } = useRemixRouteContext ( ) ; let path = useResolvedPath ( action ) ; let search = path . search ; let isIndexRoute = id . endsWith ( "/index" ) ; if ( action === "." && isIndexRoute && isActionRequestMethod ( method ) ) { search = search ? search . replace ( /^\?/ , "?index&" ) : "?index" ; } return path . pathname + search ; } export interface SubmitOptions { /**
     * The HTTP method used to submit the form. Overrides `<form method>`.
     * Defaults to "GET".
     */ method ? : FormMethod ; /**
     * The action URL path used to submit the form. Overrides `<form action>`.
     * Defaults to the path of the current route.
     *
     * Note: It is assumed the path is already resolved. If you need to resolve a
     * relative path, use `useFormAction`.
     */ action ? : string ; /**
     * The action URL used to submit the form. Overrides `<form encType>`.
     * Defaults to "application/x-www-form-urlencoded".
     */ encType ? : FormEncType ; /**
     * Set `true` to replace the current entry in the browser's history stack
     * instead of creating a new one (i.e. stay on "the same page"). Defaults
     * to `false`.
     */ replace ? : boolean ; } /**
 * Submits a HTML `<form>` to the server without reloading the page.
 */ export interface SubmitFunction { ( /**
     * Specifies the `<form>` to be submitted to the server, a specific
     * `<button>` or `<input type="submit">` to use to submit the form, or some
     * arbitrary data to submit.
     *
     * Note: When using a `<button>` its `name` and `value` will also be
     * included in the form data that is submitted.
     */ target , /**
     * Options that override the `<form>`'s own attributes. Required when
     * submitting arbitrary data without a backing `<form>`.
     */ options ? ) : void ; } /**
 * Returns a function that may be used to programmatically submit a form (or
 * some arbitrary data) to the server.
 */ export function useSubmit ( ) { return useSubmitImpl ( ) ; } export function useSubmitImpl ( key ? ) { let navigate = useNavigate ( ) ; let defaultAction = useFormAction ( ) ; let { transitionManager } = useRemixEntryContext ( ) ; return React . useCallback ( ( target , options = { } ) => { let method ; let action ; let encType ; let formData ; if ( isFormElement ( target ) ) { let submissionTrigger = ( options as any ) . submissionTrigger ; method = options . method || target . method ; action = options . action || target . action ; encType = options . encType || target . enctype ; formData = new FormData ( target ) ; if ( submissionTrigger && submissionTrigger . name ) { formData . append ( submissionTrigger . name , submissionTrigger . value ) ; } } else if ( isButtonElement ( target ) || ( isInputElement ( target ) && ( target . type === "submit" || target . type === "image" ) ) ) { let form = target . form ; if ( form == null ) { throw new Error ( `Cannot submit a <button> without a <form>` ) ; } method = options . method || target . getAttribute ( "formmethod" ) || form . method ; action = options . action || target . getAttribute ( "formaction" ) || form . action ; encType = options . encType || target . getAttribute ( "formenctype" ) || form . enctype ; formData = new FormData ( form ) ; if ( target . name ) { formData . set ( target . name , target . value ) ; } } else { if ( isHtmlElement ( target ) ) { throw new Error ( `Cannot submit element that is not <form>, <button>, or ` + `<input type="submit|image">` ) ; } method = options . method || "get" ; action = options . action || defaultAction ; encType = options . encType || "application/x-www-form-urlencoded" ; if ( target instanceof FormData ) { formData = target ; } else { formData = new FormData ( ) ; if ( target instanceof URLSearchParams ) { for ( let [ name , value ] of target ) { formData . set ( name , value ) ; } } else if ( target != null ) { for ( let name of Object . keys ( target ) ) { formData . set ( name , target [ name ] ) ; } } } } let { protocol , host } = window . location ; let url = new URL ( action , `${ protocol }//${ host }` ) ; if ( method . toLowerCase ( ) === "get" ) { for ( let [ name , value ] of formData ) { if ( typeof value === "string" ) { url . searchParams . set ( name , value ) ; } else { throw new Error ( `Cannot submit binary form data using GET` ) ; } } } let submission = { formData , action : url . pathname + url . search , method : method . toUpperCase ( ) , encType , key : Math . random ( ) . toString ( 36 ) . substr ( 2 , 8 ) } ; if ( key ) { transitionManager . send ( { type : "fetcher" , href : submission . action , submission , key } ) ; } else { setNextNavigationSubmission ( submission ) ; navigate ( url . pathname + url . search , { replace : options . replace } ) ; } } , [ defaultAction , key , navigate , transitionManager ] ) ; } let nextNavigationSubmission ; function setNextNavigationSubmission ( submission ) { nextNavigationSubmission = submission ; } function consumeNextNavigationSubmission ( ) { let submission = nextNavigationSubmission ; nextNavigationSubmission = undefined ; return submission ; } function isHtmlElement ( object ) { return object != null && typeof object . tagName === "string" ; } function isButtonElement ( object ) { return isHtmlElement ( object ) && object . tagName . toLowerCase ( ) === "button" ; } function isFormElement ( object ) { return isHtmlElement ( object ) && object . tagName . toLowerCase ( ) === "form" ; } function isInputElement ( object ) { return isHtmlElement ( object ) && object . tagName . toLowerCase ( ) === "input" ; } /**
 * Setup a callback to be fired on the window's `beforeunload` event. This is
 * useful for saving some data to `window.localStorage` just before the page
 * refreshes, which automatically happens on the next `<Link>` click when Remix
 * detects a new version of the app is available on the server.
 *
 * Note: The `callback` argument should be a function created with
 * `React.useCallback()`.
 */ export function useBeforeUnload ( callback ) { React . useEffect ( ( ) => { window . addEventListener ( "beforeunload" , callback ) ; return ( ) => { window . removeEventListener ( "beforeunload" , callback ) ; } ; } , [ callback ] ) ; } export function useMatches ( ) { let { matches , routeData , routeModules } = useRemixEntryContext ( ) ; return matches . map ( match => { let { pathname , params } = match ; return { pathname , params , data : routeData [ match . route . id ] , handle : routeModules [ match . route . id ] . handle } ; } ) ; } /**
 * Returns the data from the current route's `loader`.
 */ export function useLoaderData < T = AppData > ( ) { return useRemixRouteContext ( ) . data ; } export function useActionData < T = AppData > ( ) { let { id : routeId } = useRemixRouteContext ( ) ; let { transitionManager } = useRemixEntryContext ( ) ; let { actionData } = transitionManager . getState ( ) ; return actionData ? actionData [ routeId ] : undefined ; } export function useTransition ( ) { let { transitionManager } = useRemixEntryContext ( ) ; return transitionManager . getState ( ) . transition ; } function createFetcherForm ( fetchKey ) { return React . forwardRef < HTMLFormElement , FormProps > ( ( props , ref ) => { return < FormImpl { ... props } ref = { ref } fetchKey = { fetchKey } / > ; } ) ; } let fetcherId = 0 ; type FetcherWithComponents < TData > = Fetcher < TData > & { Form : ReturnType < typeof createFetcherForm > ; submit : ReturnType < typeof useSubmitImpl > ; load : ( href : string ) => void ; } ; /**
 * Interacts with route loaders and actions without causing a navigation. Great
 * for any interaction that stays on the same page.
 */ export function useFetcher < TData = any > ( ) { let { transitionManager } = useRemixEntryContext ( ) ; let [ key ] = React . useState ( ( ) => String ( ++ fetcherId ) ) ; let [ Form ] = React . useState ( ( ) => createFetcherForm ( key ) ) ; let [ load ] = React . useState ( ( ) => ( href ) => { transitionManager . send ( { type : "fetcher" , href , key } ) ; } ) ; let submit = useSubmitImpl ( key ) ; let fetcher = transitionManager . getFetcher < TData > ( key ) ; let fetcherWithComponents = React . useMemo ( ( ) => ( { Form , submit , load , ... fetcher } ) , [ fetcher , Form , submit , load ] ) ; React . useEffect ( ( ) => { return ( ) => transitionManager . deleteFetcher ( key ) ; } , [ transitionManager , key ] ) ; return fetcherWithComponents ; } /**
 * Provides all fetchers currently on the page. Useful for layouts and parent
 * routes that need to provide pending/optimistic UI regarding the fetch.
 */ export function useFetchers ( ) { let { transitionManager } = useRemixEntryContext ( ) ; let { fetchers } = transitionManager . getState ( ) ; return [ ... fetchers . values ( ) ] ; } export function LiveReload ( { port = 8002 } ) { if ( process . env . NODE_ENV !== "development" ) return null ; return ( < script dangerouslySetInnerHTML = { { __html : `
          let ws = new WebSocket("ws://localhost:${ port }/socket");
          ws.onmessage = message => {
            let event = JSON.parse(message.data);
            if (event.type === "LOG") {
              console.log(event.message);
            }
            if (event.type === "RELOAD") {
              console.log("💿 Reloading window ...");
              window.location.reload();
            }
          };
          ws.onerror = error => {
            console.log("Remix dev asset server web socket error:");
            console.error(error);
          };
      ` } } />); } function useComposedRefs < RefValueType = any > ( ... refs ) { return React . useCallback ( node => { for ( let ref of refs ) { if ( ref == null ) continue ; if ( typeof ref === "function" ) { ref ( node ) ; } else { try { ( ref as React . MutableRefObject < RefValueType > ) . current = node ! ; } catch ( _ ) { } } } } , refs ) ; }