Skip to content

Instantly share code, notes, and snippets.

@chaance
Last active January 10, 2023 02:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chaance/bc76129e0c1a4081b78fedcb375b22ab to your computer and use it in GitHub Desktop.
Save chaance/bc76129e0c1a4081b78fedcb375b22ab to your computer and use it in GitHub Desktop.
Convert `useNavigation` to `useTransition`
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