Route Locking

We've all experienced the frustration of filling out a long form, accidentally hitting the back button or clicking a link, and losing all our unsaved work.

Route Locking is a feature that allows you to temporarily "lock" the user on the current page. While the route is locked, any attempt to navigate away using in-app navigation—whether by clicking a <Link>, or calling router.navigate()—will be blocked.

(Note: Route locking only controls navigation handled by Retend's router. It does not prevent browser-level actions like closing the tab, typing a new URL into the address bar, or refreshing the page. For those, use the browser's beforeunload event.)

Locking a Route

To lock the current route, use the router.lock() method:

import { useRouter } from 'retend/router';

export function EditPostForm() {
  const router = useRouter();

  const handleInput = () => {
    // As soon as the user types something, lock them on the page
    router.lock();
  };

  return (
    <form>
      <textarea onInput={handleInput} placeholder="Write your post..." />
    </form>
  );
}

Once router.lock() is called, the user cannot leave the page.

Unlocking a Route

When the user finishes their task (for example, by finally clicking the "Save" button), you need to unlock the route so they can navigate normally again. Call router.unlock():

import { useRouter } from 'retend/router';
import { Cell } from 'retend';

export function SettingsForm() {
  const router = useRouter();
  const isDirty = Cell.source(false);

  const handleChange = () => {
    if (!isDirty.get()) {
      isDirty.set(true);
      router.lock();
    }
  };

  const handleSave = async () => {
    await saveSettings();
    isDirty.set(false);

    // Now that they've saved, they are free to leave
    router.unlock();
  };

  return (
    <form>
      <input type="text" onInput={handleChange} />
      <button type="button" onClick={handleSave}>
        Save
      </button>
    </form>
  );
}

Handling Blocked Navigation

Silently blocking someone from clicking a link is confusing; they'll think your website is broken. Instead, when navigation is blocked, you should show them a warning (like "You have unsaved changes!").

Retend fires a routelockprevented event on the router whenever a navigation attempt is blocked. You can listen for this event and show a popup:

import { useRouter } from 'retend/router';
import { onSetup, Cell, If } from 'retend';

export function ProtectedForm() {
  const router = useRouter();
  const showWarning = Cell.source(false);

  onSetup(() => {
    // This runs whenever a navigation is blocked
    const handleBlockedNavigation = (event: Event) => {
      showWarning.set(true);
    };

    router.addEventListener('routelockprevented', handleBlockedNavigation);

    // Always clean up your listeners!
    return () => {
      router.removeEventListener('routelockprevented', handleBlockedNavigation);
    };
  });

  const handleDiscard = () => {
    router.unlock();
    showWarning.set(false);
    // You could also manually navigate them away here if you wanted
  };

  return (
    <div>
      <textarea placeholder="Type something..."></textarea>

      {/* Show the warning dialog if navigation was blocked */}
      {If(showWarning, {
        true: () => (
          <div class="confirmation-dialog">
            <p>You have unsaved changes.</p>
            <button type="button" onClick={() => showWarning.set(false)}>
              Keep Editing
            </button>
            <button type="button" onClick={handleDiscard}>
              Discard & Leave
            </button>
          </div>
        ),
      })}
    </div>
  );
}

Route locking removes the stress of losing work and makes your web application feel as solid and reliable as a native desktop app.