Route Locking

Route locking temporarily blocks in-app navigation from the current route. While locked, navigation through a <Link> or router.navigate() is blocked.

Route locking only controls navigation handled by Retend’s router. It does not prevent browser-level actions such as closing the tab, entering a URL in the address bar, or refreshing the page. Use the browser’s beforeunload event for those actions.

Locking a Route

Call router.lock() to lock the current route:

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>
  );
}

After router.lock() is called, in-app navigation away from the route is blocked.

Unlocking a Route

Call router.unlock() when the user has finished, such as after saving a form, to allow in-app navigation again:

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);

    // Allow navigation after the save completes
    router.unlock();
  };

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

Handling Blocked Navigation

When navigation is blocked, display a message explaining why, such as a warning about unsaved changes.

Retend dispatches a routelockprevented event on the router when it blocks a navigation attempt. Listen for this event to display a warning:

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>
  );
}

Use route locking to prevent users from leaving a route with unsaved work through in-app navigation.