useTransition
useNavigation
. You can start using the new useNavigation
hook today to make upgrading in the future easy, but you can keep using useTransition
until v2.
This hook tells you everything you need to know about a page transition to build pending navigation indicators and optimistic UI on data mutations. Things like:
import { useTransition } from "@remix-run/react";
function SomeComponent() {
const transition = useTransition();
transition.state;
transition.type;
transition.submission;
transition.location;
}
transition.state
You can know the state of the transition with transition.state
. It will be one of:
Normal navigation's transition as follows:
idle → loading → idle
GET form submissions transition as follows:
idle → submitting → idle
Form submissions with POST, PUT, PATCH, or DELETE transition as follows:
idle → submitting → loading → idle
function SubmitButton() {
const transition = useTransition();
const text =
transition.state === "submitting"
? "Saving..."
: transition.state === "loading"
? "Saved!"
: "Go";
return <button type="submit">{text}</button>;
}
transition.type
Most pending UI only cares about transition.state
, but the transition can tell you even more information on transition.type
.
Remix calls your route loaders at various times, like on normal link clicks or after a form submission completes. If you'd like to build pending indication that is more granular than "loading" and "submitting", use the transition.type
.
Depending on the transition state, the types can be the following:
state === "idle"
state === "submitting"
state === "loading"
function SubmitButton() {
const transition = useTransition();
const loadTexts = {
actionRedirect: "Data saved, redirecting...",
actionReload: "Data saved, reloading fresh data...",
};
const text =
transition.state === "submitting"
? "Saving..."
: transition.state === "loading"
? loadTexts[transition.type] || "Loading..."
: "Go";
return <button type="submit">{text}</button>;
}
transition.type
The type
field has been removed in the new useNavigation
hook (which will replace useTransition
in Remix v2). We've found that state
is sufficient for almost all use-cases, and when it's not you can derive sub-types via navigation.state
and other fields. Also note that the loaderSubmission
type is now represented with state: "loading"
. Here's a few examples:
function Component() {
let navigation = useNavigation();
let isActionSubmission =
navigation.state === "submitting";
let isActionReload =
navigation.state === "loading" &&
navigation.formMethod != null &&
navigation.formMethod != "get" &&
// We had a submission navigation and are loading the submitted location
navigation.formAction === navigation.pathname;
let isActionRedirect =
navigation.state === "loading" &&
navigation.formMethod != null &&
navigation.formMethod != "get" &&
// We had a submission navigation and are now navigating to different location
navigation.formAction !== navigation.pathname;
let isLoaderSubmission =
navigation.state === "loading" &&
navigation.state.formMethod === "get" &&
// We had a loader submission and are navigating to the submitted location
navigation.formAction === navigation.pathname;
let isLoaderSubmissionRedirect =
navigation.state === "loading" &&
navigation.state.formMethod === "get" &&
// We had a loader submission and are navigating to a new location
navigation.formAction !== navigation.pathname;
}
transition.submission
Any transition that started from a <Form>
or useSubmit
will have your form's submission attached to it. This is primarily useful to build "Optimistic UI" with the submission.formData
FormData
object.
transition.submission
The submission
field has been removed in the new useNavigation
hook (which will replace useTransition
in Remix v2) and the same sub-fields are now exposed directly on the navigation
:
function Component() {
let navigation = useNavigation();
// navigation.formMethod
// navigation.formAction
// navigation.formData
// navigation.formEncType
}
transition.location
This tells you what the next location is going to be. It's most useful when matching against the next URL for custom links and hooks.
For example, this Link
knows when its page is loading and about to become active:
import { Link, useResolvedPath } from "@remix-run/react";
function PendingLink({ to, children }) {
const transition = useTransition();
const path = useResolvedPath(to);
const isPending =
transition.state === "loading" &&
transition.location.pathname === path.pathname;
return (
<Link
data-pending={isPending ? "true" : null}
to={to}
children={children}
/>
);
}
Note that this link will not appear "pending" if a form is being submitted to the URL the link points to, because we only do this for "loading" states. The form will contain the pending UI for when the state is "submitting", once the action is complete, then the link will go pending.