BaseLink - greydragon888/real-router GitHub Wiki

BaseLink (Removed)

Deprecated: BaseLink was removed. Use Link directly — it supports all props including routeOptions. See Link.

1. Overview

  • Name: BaseLink
  • Status: Removed — all functionality merged into Link
  • Migration: Replace BaseLink with Link inside RouterProvider. The routeOptions prop is now supported directly on Link.

2. Import and Basic Usage

import { BaseLink, useRouter } from "@real-router/react";

function Navigation() {
  const router = useRouter();

  return (
    <nav>
      <BaseLink router={router} routeName="home">
        Home
      </BaseLink>
      <BaseLink router={router} routeName="users" activeClassName="current">
        Users
      </BaseLink>
    </nav>
  );
}

3. Props

Prop Type Required Default Description
router Router Yes Router instance
routeName string Yes Target route name
routeParams Params No {} Route parameters
routeOptions object No {} Navigation options (reload, replace)
className string No CSS class
activeClassName string No "active" CSS class for active state
activeStrict boolean No false Strict activity check
ignoreQueryParams boolean No true Ignore query params when checking activity
onClick (evt) => void No Custom click handler
target string No HTML target attribute
children ReactNode No Link content

Props Details

routeName

Target route name from router configuration.

<BaseLink router={router} routeName="users.profile">
  Profile
</BaseLink>

routeParams

Parameters for URL building. Must match parameters in route definition.

<BaseLink router={router} routeName="users.profile" routeParams={{ id: "123" }}>
  User 123
</BaseLink>

routeOptions

Router navigation options.

<BaseLink
  router={router}
  routeName="checkout"
  routeOptions={{ replace: true }}  // Replaces current history entry
>
  Checkout
</BaseLink>

<BaseLink
  router={router}
  routeName="dashboard"
  routeOptions={{ reload: true }}  // Forces reload
>
  Dashboard
</BaseLink>

activeClassName and activeStrict

Managing CSS class for active state.

// Non-strict check (default): "users" active for "users", "users.list", "users.profile"
<BaseLink
  router={router}
  routeName="users"
  activeClassName="nav-active"
  activeStrict={false}
>
  Users
</BaseLink>

// Strict check: "users" active only for "users"
<BaseLink
  router={router}
  routeName="users"
  activeClassName="nav-active"
  activeStrict={true}
>
  Users
</BaseLink>

ignoreQueryParams

Managing query parameter check when determining activity.

// Ignores query params (default): active for /users?page=1
<BaseLink
  router={router}
  routeName="users"
  routeParams={{ id: "123" }}
  ignoreQueryParams={true}
>
  User
</BaseLink>

// Checks query params: requires exact match
<BaseLink
  router={router}
  routeName="users"
  routeParams={{ id: "123", sort: "name" }}
  ignoreQueryParams={false}
>
  User
</BaseLink>

onClick

Custom click handler. Called before navigation.

<BaseLink
  router={router}
  routeName="logout"
  onClick={(evt) => {
    // Can cancel navigation
    if (!confirm("Are you sure?")) {
      evt.preventDefault();
    }
  }}
>
  Logout
</BaseLink>

4. Children

  • Type: ReactNode
  • Description: Link content — text, icons, other components
<BaseLink router={router} routeName="home">
  <Icon name="home" />
  <span>Home</span>
</BaseLink>

5. Rendering

What It Renders

  • HTML element: <a>
  • Attributes: href, className, onClick, data-route, data-active, plus all passed props
<a
  href="/users/123"
  class="nav-link active"
  data-route="users.profile"
  data-active="true"
>
  User Profile
</a>

Data Attributes

Attribute Value Description
data-route routeName Route name for event delegation
data-active true/false Activity state for CSS selectors

Conditional Rendering

Component always renders an <a> element.

6. Styling

CSS Classes

Class Condition Description
className Always Base CSS class
activeClassName When route is active Added to className

Styling Examples

// Basic styling
<BaseLink
  router={router}
  routeName="home"
  className="nav-link"
  activeClassName="nav-link--active"
>
  Home
</BaseLink>
/* CSS */
.nav-link {
  color: gray;
}
.nav-link--active {
  color: blue;
  font-weight: bold;
}

/* Or using data attributes */
a[data-active="true"] {
  color: blue;
}

7. Accessibility

  • Semantics: Renders native <a> element with correct href
  • Keyboard navigation: Natively supported (Tab, Enter)
  • Screen readers: Correctly read as link

8. Dependencies and Context

Required Providers

Provider Required Description
RouterProvider Yes For active route detection to work

Used Hooks

  • useIsActiveRoute — route activity check (internal hook, backed by cached createActiveRouteSource)
  • useMemo, useCallback — render optimization

Params stabilization is handled inside @real-router/sources (canonicalJson cache key in createActiveRouteSource) — inline routeParams literals don't defeat the cache.

9. Events

Event Signature Description
onClick (evt: MouseEvent<HTMLAnchorElement>) => void Link click

Click Handling Logic

  1. onClick is called (if provided)
  2. If evt.defaultPrevented — navigation is cancelled
  3. shouldNavigate() is checked (left button, no modifiers)
  4. If target="_blank" — navigation is not intercepted
  5. router.navigate() is called

10. Behavior

Main Scenarios

  • Renders <a> with correct href based on routeName and routeParams
  • Adds activeClassName when route is active
  • Calls onClick on click
  • Navigates on left click without modifiers

Edge Cases

  • Does not navigate on right/middle mouse button
  • Does not navigate on click with Ctrl/Cmd/Shift/Alt
  • Does not navigate if onClick called preventDefault()
  • Does not navigate if target="_blank"
  • Uses buildUrl (if available) or buildPath for URL building
  • Updates href when routeName or routeParams change

Guarantees

  • Memoization via React.memo with custom comparator
  • Active-route cache in @real-router/sources is key-order-insensitive (canonical params hashing) — inline routeParams objects don't create duplicate subscriptions
  • Correct activeClassName operation with query parameters

11. Related Components

Component/Hook Relationship
Link Wrapper over BaseLink with automatic router retrieval
useIsActiveRoute Used for activity check (internal hook)
useRouter For getting router instance

12. Usage Examples

Basic Example

import { BaseLink, useRouter } from "@real-router/react";

function NavLink({ to, children }) {
  const router = useRouter();

  return (
    <BaseLink router={router} routeName={to}>
      {children}
    </BaseLink>
  );
}

Navigation with Parameters

function UserLink({ userId, userName }) {
  const router = useRouter();

  return (
    <BaseLink
      router={router}
      routeName="users.profile"
      routeParams={{ id: userId }}
      activeClassName="highlighted"
    >
      {userName}
    </BaseLink>
  );
}

Navigation Result Handling

For per-navigation result handling, use router.navigate() directly instead of BaseLink:

function CheckoutButton() {
  const router = useRouter();
  const [isNavigating, setIsNavigating] = useState(false);

  const handleClick = async () => {
    setIsNavigating(true);
    try {
      await router.navigate("checkout");
      analytics.track("checkout_started");
    } catch (err) {
      toast.error(`Cannot proceed: ${err.message}`);
    } finally {
      setIsNavigating(false);
    }
  };

  return (
    <button onClick={handleClick} disabled={isNavigating}>
      {isNavigating ? "Loading..." : "Proceed to Checkout"}
    </button>
  );
}

Confirmation Before Navigation

function DeleteLink({ itemId }) {
  const router = useRouter();

  return (
    <BaseLink
      router={router}
      routeName="items.delete"
      routeParams={{ id: itemId }}
      onClick={(evt) => {
        if (!window.confirm("Delete this item?")) {
          evt.preventDefault();
        }
      }}
    >
      Delete
    </BaseLink>
  );
}

Anti-patterns

// Don't pass new routeParams object on every render
function Bad({ userId }) {
  const router = useRouter();
  return (
    <BaseLink
      router={router}
      routeName="users.profile"
      routeParams={{ id: userId }} // New object every time!
    >
      Profile
    </BaseLink>
  );
}

// Optional: memoize params for custom comparators / manual subscriptions.
// For BaseLink itself, canonical-args cache in sources handles this already.
function Good({ userId }) {
  const router = useRouter();
  const params = useMemo(() => ({ id: userId }), [userId]);

  return (
    <BaseLink router={router} routeName="users.profile" routeParams={params}>
      Profile
    </BaseLink>
  );
}
// Don't forget RouterProvider
function Bad() {
  const router = createRouter(routes);
  return (
    <BaseLink router={router} routeName="home">
      Home
    </BaseLink>
  );
  // Active route detection inside will throw error!
}

// Wrap in RouterProvider
function Good() {
  return (
    <RouterProvider router={router}>
      <Navigation />
    </RouterProvider>
  );
}
// Don't use for external links
function Bad() {
  return (
    <BaseLink router={router} routeName="https://google.com">
      Google
    </BaseLink>
  );
}

// Use regular <a> for external links
function Good() {
  return (
    <a href="https://google.com" target="_blank" rel="noopener">
      Google
    </a>
  );
}

Migration from router5

Version Comparison

router5 Real Router
File modules/BaseLink.ts modules/components/BaseLink.tsx
Architecture Class Component FC + memo with custom comparator
Props types BaseLinkProps extends HTMLAttributes<...> BaseLinkProps with [key: string]: unknown
Renders <a> without data attributes <a> with data-route and data-active

1. Breaking Changes

Severity Levels:

  • CRITICAL — Component will definitely break
  • HIGH — Component will likely break
  • MEDIUM — Component may break in some cases
  • LOW — Backward compatible, but needs attention

Changes Table

Severity What Changed Was Now Impact
HIGH CSS class order "active className" "className active" CSS selectors with :first-child or class order
CRITICAL successCallback/errorCallback removed Accepted props Removed — use router.navigate() with try/catch Code using navigation callbacks on links will break
MEDIUM Prop route removed Accepted Ignored Code passing route
LOW Data attributes added Missing data-route, data-active CSS/JS relying on DOM structure

Props Changes

Prop Change Migration
route Removed Remove passing this prop
onMouseOver Moved to rest props Works, but not in explicit types
successCallback Removed Use router.navigate() with try/catch
errorCallback Removed Use router.navigate() with try/catch
routeParams Type { [key: string]: any }Params Use types from @real-router/core

Rendering Changes

Aspect Was Now
Class order activeClassName className className activeClassName
Data attributes Missing data-route={routeName} data-active={isActive}
className formation split(' ').join(' ') Template literal with .trim()

Examples

// Code that may break (CSS selectors by order)
.active:first-child { /* In router5 "active" was first */ }

// Code after migration (use independent selectors)
.active { /* Works regardless of order */ }
a[data-active="true"] { /* New way via data attribute */ }
// Code that will break (passing route)
<BaseLink router={router} routeName="home" route={currentRoute}>
  Home
</BaseLink>

// Code after migration (route not needed)
<BaseLink router={router} routeName="home">
  Home
</BaseLink>

2. Implementation Changes

Removed Props

  • route: Internal logic reworked, prop no longer used
  • successCallback: Removed — use router.navigate() with try/catch
  • errorCallback: Removed — use router.navigate() with try/catch

New Props

  • children: Explicitly added to types (previously inherited from HTMLAttributes)

Rendering Changes

  • data-route: Added attribute with route name for event delegation
  • data-active: Added attribute with boolean activity state for CSS selectors
  • Class order: Changed from activeClassName + className to className + activeClassName

Architectural Changes

  • Class → FC: Transition from class component to functional
  • memo: Added memoization with custom comparator for render optimization
  • Canonical params cache in sources: Reference stabilization for routeParams lives inside createActiveRouteSource (canonicalJson), shared by all adapters
  • useIsActiveRoute: Activity check logic extracted to separate internal hook
  • useMemo/useCallback: Memoization of href, className and handleClick

Typing Improvements

  • Params: Using type from @real-router/core instead of { [key: string]: any }

3. Dependency Changes

Dependency Was Now
router5 Yes No
@real-router/core No Yes
React hooks Not used useMemo, useCallback, memo
useIsActiveRoute Didn't exist Used for activity check (internal)
@real-router/sources Didn't exist Provides cached createActiveRouteSource with canonicalJson params hashing

4. Migration Guide

Checklist

  • Remove passing prop route (if used)
  • Remove successCallback and errorCallback props — use router.navigate() with try/catch instead
  • Check CSS selectors depending on class order
  • Update imports from router5 to @real-router/core
  • Check CSS/JS relying on absence of data attributes

Step-by-Step Migration

  1. Update imports: Replace router5 with @real-router/core in types and imports
  2. Remove route prop: Find all places where route is passed and remove
  3. Remove callback props: Replace successCallback/errorCallback with router.navigate() using try/catch
  4. Check CSS: Ensure CSS selectors don't depend on class order
  5. Use data attributes: Optionally — can use data-active instead of activeClassName for styling

5. Summary

Category Status
Breaking Changes CRITICAL (callback removal), HIGH (class order)
New props None
Removed props route, successCallback, errorCallback
DOM changes data attributes
Accessibility No changes

Maximum severity: CRITICAL — successCallback/errorCallback props removed; use router.navigate() with try/catch instead.

⚠️ **GitHub.com Fallback** ⚠️