All skills
remix-run avatar

/react-router-declarative-mode

@7c69689
by Remixremix-run/agent-skills136 stars
4

Build React applications using React Router's declarative mode with BrowserRouter. Use when configuring routes with JSX, navigating with Link/NavLink, or reading URL params and search params without data loaders or actions.

Use this Skill: https://skilld.dev/gh/remix-run/agent-skills/react-router-declarative-mode

This session only. Nothing lands on disk.

referencesurl-values.md

≈2.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

URL Values

React Router provides hooks to read values from the current URL: route params, search params (query string), and the location object.

useParams

Read dynamic segments from the route path:

// Route: <Route path="users/:userId" element={<User />} />
// URL: /users/123

import { useParams } from "react-router";

function User() {
  const { userId } = useParams();
  // userId = "123"
  return <h1>User {userId}</h1>;
}

Multiple Params

// Route: <Route path="teams/:teamId/members/:memberId" element={<Member />} />
// URL: /teams/abc/members/456

function Member() {
  const { teamId, memberId } = useParams();
  // teamId = "abc", memberId = "456"
  return (
    <h1>
      Member {memberId} of Team {teamId}
    </h1>
  );
}

Splat Params

// Route: <Route path="files/*" element={<FileViewer />} />
// URL: /files/docs/intro.md

function FileViewer() {
  const { "*": filePath } = useParams();
  // filePath = "docs/intro.md"
  return <div>Viewing: {filePath}</div>;
}

Type Safety

Params are always string | undefined. Parse and validate as needed:

function User() {
  const { userId } = useParams();

  // ❌ DON'T: Assume params exist or are numbers
  const id = parseInt(userId); // userId could be undefined

  // ✅ DO: Handle undefined and validate
  if (!userId) {
    return <div>User ID required</div>;
  }
  const id = parseInt(userId, 10);
  if (isNaN(id)) {
    return <div>Invalid user ID</div>;
  }

  return <h1>User {id}</h1>;
}

useSearchParams

Read and update the query string (?key=value):

// URL: /search?q=react&page=2

import { useSearchParams } from "react-router";

function SearchResults() {
  const [searchParams, setSearchParams] = useSearchParams();

  const query = searchParams.get("q"); // "react"
  const page = searchParams.get("page"); // "2"
  const missing = searchParams.get("foo"); // null

  return <div>Searching for: {query}</div>;
}

searchParams is a URLSearchParams object:

Updating Search Params

function SearchForm() {
  const [searchParams, setSearchParams] = useSearchParams();
  const query = searchParams.get("q") || "";

  function handleSearch(e: React.ChangeEvent<HTMLInputElement>) {
    const value = e.target.value;
    const isFirstSearch = !searchParams.has("q");

    if (value) {
      setSearchParams(
        { q: value },
        { replace: !isFirstSearch }, // Replace while typing, push on first search
      );
    } else {
      setSearchParams({}, { replace: true });
    }
  }

  return (
    <input
      type="search"
      value={query}
      onChange={handleSearch}
      placeholder="Search..."
    />
  );
}

Tip: Use replace: true for incremental updates like type-ahead search. This prevents the back button from cycling through every keystroke.

Navigation Options

setSearchParams accepts navigation options as a second argument:

function Filters() {
  const [searchParams, setSearchParams] = useSearchParams();

  function applyFilter(category: string) {
    setSearchParams(
      { category },
      {
        replace: true, // Don't add to history (back button skips)
        preventScrollReset: true, // Keep scroll position
      },
    );
  }

  return (
    <button onClick={() => applyFilter("electronics")}>Electronics</button>
  );
}
Option Purpose
replace Replace history entry instead of pushing
preventScrollReset Keep current scroll position after navigation
state Pass state to the new location

Preserving Other Params

When updating search params programmatically, preserve existing params:

function SortDropdown() {
  const [searchParams, setSearchParams] = useSearchParams();
  const sort = searchParams.get("sort") || "name";

  function handleSortChange(newSort: string) {
    // ❌ DON'T: Overwrite all params
    setSearchParams({ sort: newSort });

    // ✅ DO: Preserve existing params (like page, filters)
    setSearchParams((prev) => {
      prev.set("sort", String(newSort));
      return prev;
    });
  }

  return (
    <select value={sort} onChange={(e) => handleSortChange(e.target.value)}>
      <option value="name">Name</option>
      <option value="date">Date</option>
    </select>
  );
}

Note: For pagination and other navigation controls, prefer the Link-based approach below for better accessibility.

Link-based Pagination (Preferred)

For pagination and similar controls, prefer <Link> over buttons with setSearchParams:

import { Link, useSearchParams } from "react-router";

function Pagination({
  currentPage,
  totalPages,
}: {
  currentPage: number;
  totalPages: number;
}) {
  const [searchParams] = useSearchParams();

  // Build URL that preserves existing search params
  function getPageUrl(page: number): string {
    const params = new URLSearchParams(searchParams);
    params.set("page", String(page));
    return `?${params.toString()}`;
  }

  return (
    <nav aria-label="Pagination">
      {currentPage > 1 && (
        <Link to={getPageUrl(currentPage - 1)}>Previous</Link>
      )}
      <span>
        Page {currentPage} of {totalPages}
      </span>
      {currentPage < totalPages && (
        <Link to={getPageUrl(currentPage + 1)}>Next</Link>
      )}
    </nav>
  );
}

Why prefer Links over buttons:

  • Accessibility: Real anchor elements work with screen readers and keyboard navigation
  • Right-click: Users can open in new tab or copy link
  • Middle-click: Opens in new tab automatically
  • Browser features: Back/forward, bookmarking work naturally
  • SEO: Search engines can discover paginated content
// ❌ DON'T: Use buttons for navigation that changes URL
<button onClick={() => setSearchParams({ page: "2" })}>
  Page 2
</button>

// ✅ DO: Use Links for URL-changing navigation
<Link to="?page=2">Page 2</Link>

Multiple Values

// URL: /products?tag=react&tag=typescript

function Products() {
  const [searchParams] = useSearchParams();

  // Get all values for a key
  const tags = searchParams.getAll("tag");
  // tags = ["react", "typescript"]

  return <div>Tags: {tags.join(", ")}</div>;
}

Default Values

function ProductList() {
  const [searchParams] = useSearchParams({
    page: "1",
    sort: "name",
  });

  const page = searchParams.get("page"); // "1" if not in URL
  const sort = searchParams.get("sort"); // "name" if not in URL

  return (
    <div>
      Page {page}, sorted by {sort}
    </div>
  );
}

useLocation

Access the full location object:

import { useLocation } from "react-router";

function CurrentPath() {
  const location = useLocation();

  return <pre>{JSON.stringify(location, null, 2)}</pre>;
}

Location Object Properties

Property Type Description Example
pathname string The path portion of the URL "/users/123"
search string The query string (including ?) "?q=react&page=2"
hash string The hash portion (including #) "#section-1"
state any State passed during navigation { from: "/home" }
key string Unique key for this location "default" or random key
// URL: /search?q=react#results
// Navigated with state: { from: "/home" }

function SearchPage() {
  const location = useLocation();

  console.log(location.pathname); // "/search"
  console.log(location.search); // "?q=react"
  console.log(location.hash); // "#results"
  console.log(location.state); // { from: "/home" }
  console.log(location.key); // "abc123" (unique key)
}

Reading Navigation State

State passed via <Link state={...}> or navigate(path, { state }):

// Sender
<Link to="/checkout" state={{ cartId: "abc", from: "/cart" }}>
  Checkout
</Link>;

// Receiver
function Checkout() {
  const location = useLocation();
  const { cartId, from } = location.state || {};

  return (
    <div>
      <h1>Checkout</h1>
      {from && <Link to={from}>← Back</Link>}
      <p>Cart: {cartId}</p>
    </div>
  );
}

Watching Location Changes

import { useEffect } from "react";
import { useLocation } from "react-router";

function Analytics() {
  const location = useLocation();

  useEffect(() => {
    // Track page views on route change
    trackPageView(location.pathname);
  }, [location]);

  return null;
}

Combining Hooks

// URL: /products/shoes?color=red&size=10#reviews

function ProductPage() {
  const { category } = useParams(); // "shoes"
  const [searchParams] = useSearchParams();
  const location = useLocation();

  const color = searchParams.get("color"); // "red"
  const size = searchParams.get("size"); // "10"
  const hash = location.hash; // "#reviews"

  return (
    <div>
      <h1>{category}</h1>
      <p>
        Color: {color}, Size: {size}
      </p>
      {hash === "#reviews" && <Reviews />}
    </div>
  );
}

See Also

Source: SKILL.md on GitHub

1 warning15d5 checks · Risk SAFE
  • Gen Agent Trust Hub15d

    The skill provides comprehensive documentation and best practices for using React Router's declarative mode. It covers route configuration, navigation components, and hooks for reading URL values. No security issues or malicious patterns were detected.

  • Socket15d

    No alerts

  • Snyk15d

    Risk: LOW · No issues

  • Runlayer7mo

    4/4 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 7c69689. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Steadyupdated 8 months ago

README badge

README badge for remix-run/agent-skills/react-router-declarative-mode