Last active
January 10, 2023 02:00
-
-
Save chaance/bc76129e0c1a4081b78fedcb375b22ab to your computer and use it in GitHub Desktop.
Convert `useNavigation` to `useTransition`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from "react"; | |
import { useNavigation, type Navigation } from "@remix-run/react"; | |
export function useTransition(): Transition { | |
let navigation = useNavigation(); | |
return React.useMemo( | |
() => convertNavigationToTransition(navigation), | |
[navigation] | |
); | |
} | |
function convertNavigationToTransition(navigation: Navigation): Transition { | |
let { location, state, formMethod, formAction, formEncType, formData } = | |
navigation; | |
if (!location) { | |
return { | |
state: "idle", | |
submission: undefined, | |
location: undefined, | |
type: "idle", | |
}; | |
} | |
let isActionSubmission = | |
formMethod != null && | |
["POST", "PUT", "PATCH", "DELETE"].includes(formMethod.toUpperCase()); | |
if ( | |
state === "submitting" && | |
formMethod && | |
formAction && | |
formEncType && | |
formData | |
) { | |
if (isActionSubmission) { | |
// Actively submitting to an action | |
let transition: TransitionStates["SubmittingAction"] = { | |
location, | |
state, | |
submission: { | |
method: formMethod.toUpperCase() as ActionSubmission["method"], | |
action: formAction, | |
encType: formEncType, | |
formData: formData, | |
key: "", | |
}, | |
type: "actionSubmission", | |
}; | |
return transition; | |
} else { | |
throw new Error( | |
"Encountered an unexpected navigation scenario in useTransition()" | |
); | |
} | |
} | |
if (state === "loading") { | |
// NOTE: These are implementation details that may change in the future. We will | |
// work on a better solution or workaround for simpler migration in the v2. | |
let { _isRedirect, _isFetchActionRedirect } = location.state || {}; | |
if (formMethod && formAction && formEncType && formData) { | |
if (!_isRedirect) { | |
if (isActionSubmission) { | |
// We're reloading the same location after an action submission | |
let transition: TransitionStates["LoadingAction"] = { | |
location, | |
state, | |
submission: { | |
method: formMethod.toUpperCase() as ActionSubmission["method"], | |
action: formAction, | |
encType: formEncType, | |
formData: formData, | |
key: "", | |
}, | |
type: "actionReload", | |
}; | |
return transition; | |
} else { | |
// The new router fixes a bug in useTransition where the submission | |
// "action" represents the request URL not the state of the <form> in | |
// the DOM. Back-port it here to maintain behavior, but useNavigation | |
// will fix this bug. | |
let url = new URL(formAction, window.location.origin); | |
// This typing override should be safe since this is only running for | |
// GET submissions and over in @remix-run/router we have an invariant | |
// if you have any non-string values in your FormData when we attempt | |
// to convert them to URLSearchParams | |
url.search = new URLSearchParams( | |
formData.entries() as unknown as [string, string][] | |
).toString(); | |
// Actively "submitting" to a loader | |
let transition: TransitionStates["SubmittingLoader"] = { | |
location, | |
state: "submitting", | |
submission: { | |
method: formMethod.toUpperCase() as LoaderSubmission["method"], | |
action: url.pathname + url.search, | |
encType: formEncType, | |
formData: formData, | |
key: "", | |
}, | |
type: "loaderSubmission", | |
}; | |
return transition; | |
} | |
} else { | |
// Redirecting after a submission | |
if (isActionSubmission) { | |
let transition: TransitionStates["LoadingActionRedirect"] = { | |
location, | |
state, | |
submission: { | |
method: formMethod.toUpperCase() as ActionSubmission["method"], | |
action: formAction, | |
encType: formEncType, | |
formData: formData, | |
key: "", | |
}, | |
type: "actionRedirect", | |
}; | |
return transition; | |
} else { | |
let transition: TransitionStates["LoadingLoaderSubmissionRedirect"] = | |
{ | |
location, | |
state, | |
submission: { | |
method: formMethod.toUpperCase() as LoaderSubmission["method"], | |
action: formAction, | |
encType: formEncType, | |
formData: formData, | |
key: "", | |
}, | |
type: "loaderSubmissionRedirect", | |
}; | |
return transition; | |
} | |
} | |
} else if (_isRedirect) { | |
if (_isFetchActionRedirect) { | |
let transition: TransitionStates["LoadingFetchActionRedirect"] = { | |
location, | |
state, | |
submission: undefined, | |
type: "fetchActionRedirect", | |
}; | |
return transition; | |
} else { | |
let transition: TransitionStates["LoadingRedirect"] = { | |
location, | |
state, | |
submission: undefined, | |
type: "normalRedirect", | |
}; | |
return transition; | |
} | |
} | |
} | |
// If no scenarios above match, then it's a normal load! | |
let transition: TransitionStates["Loading"] = { | |
location, | |
state: "loading", | |
submission: undefined, | |
type: "normalLoad", | |
}; | |
return transition; | |
} | |
type Transition = TransitionStates[keyof TransitionStates]; | |
type TransitionStates = { | |
Idle: { | |
state: "idle"; | |
type: "idle"; | |
submission: undefined; | |
location: undefined; | |
}; | |
SubmittingAction: { | |
state: "submitting"; | |
type: "actionSubmission"; | |
submission: ActionSubmission; | |
location: Location; | |
}; | |
SubmittingLoader: { | |
state: "submitting"; | |
type: "loaderSubmission"; | |
submission: LoaderSubmission; | |
location: Location; | |
}; | |
LoadingLoaderSubmissionRedirect: { | |
state: "loading"; | |
type: "loaderSubmissionRedirect"; | |
submission: LoaderSubmission; | |
location: Location; | |
}; | |
LoadingAction: { | |
state: "loading"; | |
type: "actionReload"; | |
submission: ActionSubmission; | |
location: Location; | |
}; | |
LoadingActionRedirect: { | |
state: "loading"; | |
type: "actionRedirect"; | |
submission: ActionSubmission; | |
location: Location; | |
}; | |
LoadingFetchActionRedirect: { | |
state: "loading"; | |
type: "fetchActionRedirect"; | |
submission: undefined; | |
location: Location; | |
}; | |
LoadingRedirect: { | |
state: "loading"; | |
type: "normalRedirect"; | |
submission: undefined; | |
location: Location; | |
}; | |
Loading: { | |
state: "loading"; | |
type: "normalLoad"; | |
location: Location; | |
submission: undefined; | |
}; | |
}; | |
interface Submission { | |
action: string; | |
method: string; | |
formData: FormData; | |
encType: string; | |
key: string; | |
} | |
interface LoaderSubmission extends Submission { | |
method: "GET"; | |
} | |
interface ActionSubmission extends Submission { | |
method: "POST" | "PUT" | "PATCH" | "DELETE"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment