flemo

Navigation

useNavigate, useParams, useStep, and how each one moves you around

flemo gives you three navigation hooks. They cover different shapes of movement.

useNavigate

const navigate = useNavigate();

navigate.push("/posts/:slug", { slug: "hello" });
navigate.replace("/login");
navigate.pop(); // back one screen
navigate.pop({ skip: 2 }); // back two screens, in one transition
navigate.pop({ until: "/posts/:slug" }); // back to the nearest matching screen
navigate.pop({ until: "/", transitionName: "cupertino" }); // override the back transition

push, replace, and pop are async. They wait for the transition to start and the route to take effect. However far one reaches, it drives a single transition.

Reaching past the top

pop, replace, and push all take an optional distance ({ skip }, a number of screens, or { until }, a route pattern) to reach a screen below the top in one transition. The screens skipped over are removed without ever painting, so you never see them flash by.

{ skip: n } is the screen n below the top; { until } is the nearest screen below the top whose route matches the pattern (searched top → root, current top excluded). They reach the same target and differ only in what they do there:

MethodAt the reached target…
poplands on it. The target stays
replacereplaces it. The target and everything above become the new screen
pushkeeps it. The new screen stacks on top
// stack:  /  →  /library  →  /posts/a  →  /posts/b   (top = /posts/b)
navigate.pop({ skip: 2 }); //               → /, /library          (lands on /library)
navigate.replace("/x", {}, { skip: 2 }); // → /, /x                (/library replaced)
navigate.push("/x", {}, { skip: 2 }); //    → /, /library, /x      (/library kept)

navigate.pop({ until: "/library" }); //               → /, /library
navigate.replace("/x", {}, { until: "/library" }); // → /, /x
navigate.push("/x", {}, { until: "/library" }); //    → /, /library, /x

{ skip } and { until } are mutually exclusive (until wins if both are passed). An unmatched until is a no-op for pop / replace, and a plain push for push. { skip } clamps to the stack depth.

Options

navigate.push(
  "/posts/:slug",
  { slug: "hello" },
  {
    transitionName: "material",
    layoutId: "photo-hello"
  }
);
OptionWhat it does
transitionNameOverride the transition for this navigation. On pop it sets the back animation. Useful when collapsing several screens whose own transition isn't the one you want
layoutIdPair with transitionName: "layout" on push or replace to enable shared-element morphs
skipReach n screens past the top in one transition. See Reaching past the top
untilReach back to the nearest screen matching a route pattern. See Reaching past the top

skip, until, and transitionName are accepted by push, replace, and pop; layoutId applies to push and replace.

useParams

useParams<T>() returns the params for the current route, typed against your RegisterRoute augmentation.

function Post() {
  const { slug } = useParams<"/posts/:slug">();
  return <h1>{slug}</h1>;
}

flemo merges path params and query params into one object.

URL: /posts/hello?ref=home
useParams<"/posts/:slug">() → { slug: "hello", ref: "home" }

useStep

useStep() is for sub-states within the current screen. It pushes a new history entry (so the browser back button still goes back) but doesn't change the route. The same <Screen> stays mounted; only its params update.

A common case: a multi-step form on one screen.

function Onboarding() {
  const { step = "name" } = useParams<"/onboarding">();
  const stepper = useStep<"/onboarding">();

  if (step === "name") {
    return <button onClick={() => stepper.pushStep({ step: "email" })}>Next</button>;
  }

  if (step === "email") {
    return <button onClick={() => stepper.popStep()}>Back</button>;
  }
}
MethodWhat it does
pushStep(params)Push a new history entry with these params, same route
replaceStep(params)Replace current history entry with these params
popStep()Go back one step (same as window.history.back())

Choosing between push and step

You want to…Use
Show a different screennavigate.push
Replace the current screen (e.g., login → home)navigate.replace
Close the current screennavigate.pop
Reach several screens back at oncenavigate.pop({ skip })
Reach back to a specific earlier screennavigate.pop({ until })
Push/replace while clearing screens in between…({ skip }) / …({ until })
Stay on the same screen but change UIuseStep().pushStep

useStep keeps everything in the screen tree. Same Screen instance, same Provider, same scroll position. useNavigate mounts a new Screen with a real transition.

On this page