All skills
sergiodxa avatar

/frontend-react-router-best-practices

@9da9532

React Router performance and architecture patterns. Use when writing loaders, actions, forms, routes, or working with React Router data fetching. Triggers on tasks involving React Router routes, data loading, form handling, or route organization.

Use this Skill: https://skilld.dev/gh/sergiodxa/agent-skills/frontend-react-router-best-practices

This session only. Nothing lands on disk.

rulesaction-zod-transform.md

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

Use Zod Transform for Input Sanitization

Use Zod's .transform() to sanitize and normalize form input during validation.

Why

  • Sanitization happens at the same place as validation
  • Input is normalized before it reaches business logic
  • Type-safe: transformed type is reflected in TypeScript
  • No separate sanitization step needed

Common Transforms

Trim Whitespace

const schema = z.object({
  name: z.string().trim(),
  email: z.string().email().trim().toLowerCase(),
});

// Input: { name: "  John  ", email: "  JOHN@Example.COM  " }
// Output: { name: "John", email: "john@example.com" }

Convert Empty Strings to Undefined

const schema = z.object({
  nickname: z
    .string()
    .transform((val) => (val === "" ? undefined : val))
    .pipe(z.string().optional()),
});

// Input: { nickname: "" }
// Output: { nickname: undefined }

Parse Numbers from Strings

const schema = z.object({
  amount: z
    .string()
    .transform((val) => parseFloat(val.replace(/[,$]/g, "")))
    .pipe(z.number().positive()),
});

// Input: { amount: "$1,234.56" }
// Output: { amount: 1234.56 }

Normalize Phone Numbers

const schema = z.object({
  phone: z
    .string()
    .transform((val) => val.replace(/\D/g, ""))
    .pipe(z.string().length(10)),
});

// Input: { phone: "(555) 123-4567" }
// Output: { phone: "5551234567" }

Convert Checkbox to Boolean

const schema = z.object({
  subscribe: z
    .string()
    .optional()
    .transform((val) => val === "on"),
});

// Input: { subscribe: "on" } or {}
// Output: { subscribe: true } or { subscribe: false }

Full Example: Action with Transforms

// schemas.server.ts
import { z } from "zod";
import type { TFunction } from "i18next";

export function orderSchema(t: TFunction) {
  return z.object({
    amount: z
      .string()
      .transform((val) => parseFloat(val.replace(/[,$]/g, "")))
      .pipe(
        z
          .number({ message: t("Amount is required") })
          .positive({ message: t("Amount must be positive") })
          .max(1000000, { message: t("Amount exceeds maximum") }),
      ),

    itemId: z
      .string()
      .trim()
      .pipe(z.string().uuid({ message: t("Invalid item") })),

    note: z
      .string()
      .trim()
      .transform((val) => (val === "" ? undefined : val))
      .pipe(z.string().max(500).optional()),

    isPrivate: z
      .string()
      .optional()
      .transform((val) => val === "on"),

    email: z
      .string()
      .trim()
      .toLowerCase()
      .pipe(z.string().email({ message: t("Invalid email") })),
  });
}

// route.tsx
export async function action({ request }: Route.ActionArgs) {
  let t = await i18next.getFixedT(request);
  let formData = await request.formData();

  let result = orderSchema(t).safeParse(Object.fromEntries(formData));

  if (!result.success) {
    return data(
      { errors: result.error.flatten().fieldErrors },
      { status: 400 },
    );
  }

  // result.data is fully sanitized and typed
  const { amount, itemId, note, isPrivate, email } = result.data;

  await createOrder({ amount, itemId, note, isPrivate, email });
  return redirect("/orders");
}

Chaining Transforms

Use .pipe() to validate after transforming:

const schema = z.object({
  // Transform first, then validate the transformed value
  age: z
    .string()
    .transform((val) => parseInt(val, 10))
    .pipe(z.number().int().min(18).max(120)),
});

Default Values with Transform

const schema = z.object({
  page: z
    .string()
    .optional()
    .transform((val) => (val ? parseInt(val, 10) : 1))
    .pipe(z.number().int().positive()),

  sort: z
    .string()
    .optional()
    .transform((val) => val || "date")
    .pipe(z.enum(["date", "amount", "name"])),
});

Rules

  1. Use .trim() on all string inputs
  2. Use .toLowerCase() on emails and case-insensitive fields
  3. Use .transform() to convert form strings to proper types (numbers, booleans)
  4. Use .pipe() to validate after transforming
  5. Handle empty strings explicitly (transform to undefined or use .optional())
  6. Put transforms in schema factory functions for i18n support

Source: SKILL.md on GitHub

2 warnings16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides a comprehensive set of developer guidelines and best practices for building React Router applications. It emphasizes performance, type safety, and security measures like input validation (Zod), safe redirects, and CSRF protection (Sec-Fetch). No malicious code, obfuscation, or data exfiltration patterns were detected.

  • Socket16d

    1 alert: gptSecurity

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    56/56 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 9da9532. 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

README badge

README badge for sergiodxa/agent-skills/frontend-react-router-best-practices