All skills
softaworks avatar

/react-dev

@1c0662a
by softaworkssoftaworks/agent-toolkit2.5k stars
230

This skill should be used when building React components with TypeScript, typing hooks, handling events, or when React TypeScript, React 19, Server Components are mentioned. Covers type-safe patterns for React 18-19 including generic components, proper event typing, and routing integration (TanStack Router, React Router).

Use this Skill: https://skilld.dev/gh/softaworks/agent-toolkit/react-dev

This session only. Nothing lands on disk.

referencestanstack-router.md

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

TanStack Router TypeScript Patterns

TanStack Router provides full type safety for routes, params, search params, and loader data.

Basic Route Definition

import { createRoute, createRootRoute } from '@tanstack/react-router';

// Root route
const rootRoute = createRootRoute({
  component: RootLayout,
});

// Basic route
const indexRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/',
  component: HomePage,
});

// Route tree
const routeTree = rootRoute.addChildren([indexRoute]);

// Router
const router = createRouter({ routeTree });

// Type for router - use in app
type Router = typeof router;

Route with Params

import { createRoute } from '@tanstack/react-router';
import { useParams } from '@tanstack/react-router';

const userRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/users/$userId',
  component: UserPage,
});

function UserPage() {
  const { userId } = useParams({ from: userRoute.id });
  // userId is typed as string

  return <div>User ID: {userId}</div>;
}

// Multiple params
const postRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/users/$userId/posts/$postId',
  component: PostPage,
});

function PostPage() {
  const { userId, postId } = useParams({ from: postRoute.id });
  // Both typed as string

  return <div>Post {postId} by user {userId}</div>;
}

Search Params with Validation

import { z } from 'zod';

const productsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/products',
  component: ProductsPage,
  validateSearch: z.object({
    category: z.string().optional(),
    sortBy: z.enum(['price', 'name', 'rating']).default('name'),
    page: z.number().int().positive().default(1),
    perPage: z.number().int().positive().default(20),
  }),
});

function ProductsPage() {
  const search = useSearch({ from: productsRoute.id });
  // search is typed from Zod schema:
  // {
  //   category?: string;
  //   sortBy: 'price' | 'name' | 'rating';
  //   page: number;
  //   perPage: number;
  // }

  return (
    <div>
      Category: {search.category || 'All'}
      Sort by: {search.sortBy}
      Page: {search.page}
    </div>
  );
}

Loader Data

type User = {
  id: string;
  name: string;
  email: string;
};

const userRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/users/$userId',
  component: UserPage,
  loader: async ({ params }) => {
    const user = await fetchUser(params.userId);
    return { user };
  },
});

function UserPage() {
  const { user } = useLoaderData({ from: userRoute.id });
  // user typed as User

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

async function fetchUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

Loader with Search Params

const productsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/products',
  component: ProductsPage,
  validateSearch: z.object({
    category: z.string().optional(),
    page: z.number().default(1),
  }),
  loader: async ({ search }) => {
    // search is typed from validateSearch
    const products = await fetchProducts({
      category: search.category,
      page: search.page,
    });

    return { products };
  },
});

function ProductsPage() {
  const { products } = useLoaderData({ from: productsRoute.id });
  const search = useSearch({ from: productsRoute.id });

  return (
    <div>
      <h1>Products - {search.category || 'All'}</h1>
      <ul>
        {products.map((p) => (
          <li key={p.id}>{p.name}</li>
        ))}
      </ul>
    </div>
  );
}

Type-Safe Navigation

import { useNavigate, Link } from '@tanstack/react-router';

function ProductList() {
  const navigate = useNavigate();

  const goToProduct = (productId: string) => {
    navigate({
      to: '/products/$productId',
      params: { productId }, // Type-checked
      search: { tab: 'reviews' }, // Type-checked against validateSearch
    });
  };

  return (
    <div>
      {products.map((product) => (
        <Link
          key={product.id}
          to="/products/$productId"
          params={{ productId: product.id }}
          search={{ tab: 'details' }}
        >
          {product.name}
        </Link>
      ))}
    </div>
  );
}

Search Params Manipulation

import { useNavigate, useSearch } from '@tanstack/react-router';

const productsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/products',
  validateSearch: z.object({
    category: z.string().optional(),
    sortBy: z.enum(['price', 'name']).default('name'),
    page: z.number().default(1),
  }),
});

function ProductFilters() {
  const navigate = useNavigate({ from: productsRoute.id });
  const search = useSearch({ from: productsRoute.id });

  const updateCategory = (category: string) => {
    navigate({
      search: (prev) => ({
        ...prev,
        category, // Type-checked
        page: 1, // Reset page
      }),
    });
  };

  const updateSort = (sortBy: 'price' | 'name') => {
    navigate({
      search: (prev) => ({ ...prev, sortBy }),
    });
  };

  return (
    <div>
      <select value={search.category || ''} onChange={(e) => updateCategory(e.target.value)}>
        <option value="">All Categories</option>
        <option value="electronics">Electronics</option>
        <option value="books">Books</option>
      </select>

      <select value={search.sortBy} onChange={(e) => updateSort(e.target.value as 'price' | 'name')}>
        <option value="name">Name</option>
        <option value="price">Price</option>
      </select>
    </div>
  );
}

Nested Routes

const usersRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/users',
  component: UsersLayout,
});

const usersIndexRoute = createRoute({
  getParentRoute: () => usersRoute,
  path: '/',
  component: UsersList,
});

const userDetailRoute = createRoute({
  getParentRoute: () => usersRoute,
  path: '$userId',
  component: UserDetail,
});

const routeTree = rootRoute.addChildren([
  usersRoute.addChildren([
    usersIndexRoute,
    userDetailRoute,
  ]),
]);

// Layout component with outlet
function UsersLayout() {
  return (
    <div>
      <h1>Users</h1>
      <Outlet /> {/* Renders child route */}
    </div>
  );
}

Before Load Hook

const protectedRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/dashboard',
  beforeLoad: async ({ location }) => {
    const isAuthenticated = await checkAuth();

    if (!isAuthenticated) {
      throw redirect({
        to: '/login',
        search: { redirect: location.href },
      });
    }

    return { user: await getCurrentUser() };
  },
  component: Dashboard,
});

function Dashboard() {
  const { user } = useLoaderData({ from: protectedRoute.id });
  // user available from beforeLoad

  return <div>Welcome, {user.name}</div>;
}

Error Handling

const userRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/users/$userId',
  component: UserPage,
  errorComponent: UserErrorPage,
  loader: async ({ params }) => {
    const user = await fetchUser(params.userId);
    if (!user) {
      throw new Error('User not found');
    }
    return { user };
  },
});

function UserErrorPage({ error }: { error: Error }) {
  return (
    <div>
      <h1>Error</h1>
      <p>{error.message}</p>
      <Link to="/users">Back to users</Link>
    </div>
  );
}

Pending Component

const userRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/users/$userId',
  component: UserPage,
  pendingComponent: UserPageSkeleton,
  loader: async ({ params }) => {
    const user = await fetchUser(params.userId);
    return { user };
  },
});

function UserPageSkeleton() {
  return (
    <div>
      <div className="skeleton-header" />
      <div className="skeleton-body" />
    </div>
  );
}

Router Context

Share data across all routes.

type RouterContext = {
  auth: { user: User | null };
  queryClient: QueryClient;
};

const rootRoute = createRootRoute<RouterContext>({
  component: RootLayout,
});

const router = createRouter({
  routeTree,
  context: {
    auth: { user: null },
    queryClient: new QueryClient(),
  },
});

// Access in route
const userRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/users/$userId',
  beforeLoad: ({ context }) => {
    // context typed as RouterContext
    console.log(context.auth.user);
  },
});

Route Masks

Hide actual URL structure.

const userRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/users/$userId',
});

// Navigate with mask
navigate({
  to: '/users/$userId',
  params: { userId: '123' },
  mask: {
    to: '/profile',
  },
});

// URL shows /profile but renders /users/123

Search Param Middleware

Transform search params before validation.

const productsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/products',
  validateSearch: z.object({
    tags: z.array(z.string()).default([]),
  }),
  loaderDeps: ({ search }) => ({ tags: search.tags }),
  loader: async ({ deps }) => {
    const products = await fetchProducts({ tags: deps.tags });
    return { products };
  },
});

// URL: /products?tags=electronics&tags=sale
// search.tags = ['electronics', 'sale']

Type Helpers

import type { RouteIds, RouteById } from '@tanstack/react-router';

// Get all route IDs
type AllRouteIds = RouteIds<typeof router>;

// Get specific route type
type UserRoute = RouteById<typeof router, '/users/$userId'>;

// Extract params type
type UserParams = UserRoute['types']['allParams'];

// Extract search type
type UserSearch = UserRoute['types']['fullSearchSchema'];

Preloading Routes

import { useRouter } from '@tanstack/react-router';

function ProductCard({ productId }: { productId: string }) {
  const router = useRouter();

  const preloadProduct = () => {
    router.preloadRoute({
      to: '/products/$productId',
      params: { productId },
    });
  };

  return (
    <Link
      to="/products/$productId"
      params={{ productId }}
      onMouseEnter={preloadProduct} // Preload on hover
    >
      View Product
    </Link>
  );
}

Route Matching

import { useMatches, useMatch } from '@tanstack/react-router';

function Navigation() {
  const matches = useMatches();
  // Array of all matched routes

  const userMatch = useMatch({ from: userRoute.id, shouldThrow: false });
  // userMatch is typed, null if not matched

  return (
    <nav>
      {matches.map((match) => (
        <span key={match.id}>{match.pathname}</span>
      ))}
    </nav>
  );
}

File-Based Routing (Code Generation)

// routes/__root.tsx
export const Route = createRootRoute({
  component: RootLayout,
});

// routes/index.tsx
export const Route = createFileRoute('/')({
  component: HomePage,
});

// routes/users/$userId.tsx
export const Route = createFileRoute('/users/$userId')({
  component: UserPage,
  loader: async ({ params }) => {
    const user = await fetchUser(params.userId);
    return { user };
  },
});

// Generate route tree with CLI
// npm run generate-routes

// Import generated routes
import { routeTree } from './routeTree.gen';
const router = createRouter({ routeTree });

Integration with React Query

import { queryOptions, useQuery } from '@tanstack/react-query';

const userQueryOptions = (userId: string) =>
  queryOptions({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });

const userRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/users/$userId',
  loader: ({ context, params }) => {
    // Prefetch with React Query
    return context.queryClient.ensureQueryData(userQueryOptions(params.userId));
  },
  component: UserPage,
});

function UserPage() {
  const { userId } = useParams({ from: userRoute.id });

  // Use same query options in component
  const { data: user } = useQuery(userQueryOptions(userId));

  return <div>{user?.name}</div>;
}

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The react-dev skill is a comprehensive documentation and reference guide for building React applications with TypeScript, covering React 18 and 19 features, hooks, and routing. No security issues were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    9 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

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

Last checked against GitHub 2 months ago.

Dormantupdated 8 months ago
version
1.0.0
  • React
  • TypeScript
  • react-19
  • hooks
  • server-components
  • tanstack-router
  • react-router
  • event-handlers
  • generic-components

README badge

README badge for softaworks/agent-toolkit/react-dev

Teaches type-safe React component patterns with TypeScript across React 18-19, including generic components, event handler typing, hooks, Server Components, and router integration (TanStack Router, React Router v7). Covers React 19 breaking changes like ref-as-prop, useActionState, and use() for promise unwrapping.

Generated from the current SKILL.md.

Does this skill cover React 19?
Yes. The skill covers React 19 breaking changes including ref as prop (forwardRef deprecated), useActionState (replaces useFormState), and use() for unwrapping promises and context.
What routing libraries does this support?
TanStack Router and React Router v7. Both support type-safe routing with compile-time validation of params and search query strings.
Does this cover Server Components and Server Actions?
Yes. The skill includes patterns for async Server Components, 'use server' Server Actions, and passing promises to Client Components via use().
Can I use this with generic components?
Yes. The skill covers generic component patterns including constrained generics, keyof T for type-safe column access, and render props for custom rendering.
Does this work with non-TypeScript React?
No. The skill requires TypeScript and focuses exclusively on type-safe patterns for React 18-19.

Generated from the current SKILL.md. These answers refresh after source changes.