More screenshots

    Works with the AI Agents you already use

    Claude CodeClaude CodeCursorCursorCodex CLICodex CLIDeepSeekDeepSeekMuseMuse+22 more

    React Code Shield: PR Review for React + TypeScript

    1

    React Code Shield reviews React and TypeScript pull requests the way a careful senior engineer would.

    $12

    Secure checkout via Stripe

    Money-back guarantee

    Converts to your local currency at checkout

    Security scanned
    React Code Shield: PR Review for React + TypeScript

    React Code Shield: PR Review for React + TypeScript

    Example session with this skill installed

    Review this PR before I merge it

    <paste your diff here. This run was a 12-file, 409-line PR: comment list, login form, user search hook, live ticker, pagination, API client and a few UI components>
    
    • Read your context and instructions
    • Compiled the react code shield
    • Generated the app files

    React Code Shield Report

    BlockersShould-fixNitpicksFiles reviewed
    4909

    Verdict: FAIL

    Blockers

    1. Unsanitized HTML from API data src/components/CommentList.tsx:45 (Certain)

    • Issue: comment.body is user-generated content rendered as raw HTML, so a stored script payload would run in every viewer's browser (XSS).
    • Fix:
      import DOMPurify from "dompurify";
      <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(comment.body) }} />
      // or, if rich text isn't needed: <div>{comment.body}</div>
      

    2. Live secret key hardcoded in client code src/lib/api.ts:2 (Certain)

    • Issue: PAYMENTS_SECRET_KEY is bundled into the browser build and sent as a Bearer token (line 12), so anyone can read it from the shipped JS. The literal is labelled "DEMO", but the pattern is the same. If any real key was ever committed, rotate it.
    • Fix:
      const BASE_URL = "https://api.example.com";
      
      export async function getOrders(userId: string): Promise<Order[]> {
        // Authenticate via a server-side session or proxy; never ship secrets to the client.
        const res = await fetch(`${BASE_URL}/users/${encodeURIComponent(userId)}/orders`, {
          credentials: "include",
        });
        // ...
      }
      

    3. Unvalidated redirect query param used as href src/components/LoginForm.tsx:61 (Certain)

    • Issue: redirectUrl comes straight from ?redirect= (line 17). ?redirect=javascript:... runs script when "Skip for now" is clicked, and ?redirect=https://evil.site is an open redirect.
    • Fix:
      function safeRedirect(raw: string | null): string {
        if (raw && raw.startsWith("/") && !raw.startsWith("//") && !raw.startsWith("/\\")) {
          return raw;
        }
        return "/dashboard";
      }
      
      const redirectUrl = safeRedirect(params.get("redirect"));
      

    4. Effect sets state that is in its own dependency array src/components/Pagination.tsx:13 (Certain)

    • Issue: the effect calls setPage (lines 15 and 17) while page is in its deps. In practice setPage(page) is a same-value no-op and won't spin forever, but the pattern is a Blocker. The clamp on line 15 and the redundant line 17 should go. onPageChange in the deps also re-fires the effect every time the parent passes an inline function.
    • Fix:
      const [page, setPage] = useState(1);
      const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
      const currentPage = Math.min(page, totalPages);
      
      function goTo(next: number) {
        const clamped = Math.min(Math.max(1, next), totalPages);
        setPage(clamped);
        onPageChange(clamped);
      }
      
      // buttons: onClick={() => goTo(currentPage - 1)} / goTo(currentPage + 1)
      // disabled={currentPage <= 1} / disabled={currentPage >= totalPages}
      

    Should-fix

    1. Index used as key on a re-sortable list src/components/CommentList.tsx:43 (Certain)

    • Issue: the list reorders (newest/popular), so index keys cause React to reuse the wrong DOM nodes and state.
    • Fix:
      <li key={comment.id} className="comment-item">
      

    2. any cast and unchecked response src/lib/api.ts:14 (Certain)

    • Issue: getOrders casts the body to any and never checks res.ok, so an error response gives data.orders === undefined typed as Order[]. getOrder (line 23) also returns res.json() without validating its shape.
    • Fix:
      if (!res.ok) {
        throw new Error(`Failed to load orders (${res.status})`);
      }
      const data: unknown = await res.json();
      if (typeof data !== "object" || data === null || !Array.isArray((data as { orders?: unknown }).orders)) {
        throw new Error("Malformed orders response");
      }
      return (data as { orders: Order[] }).orders;
      

    3. Interval started with no cleanup src/components/LiveTicker.tsx:28 (Certain)

    • Issue: setInterval is never cleared. Every symbol or intervalMs change stacks another poller, and unmounting leaves one running. Old-symbol responses can also overwrite the newer quote.
    • Fix:
      useEffect(() => {
        let cancelled = false;
        const id = setInterval(() => {
          fetchQuote(symbol)
            .then((q) => {
              if (!cancelled) {
                setQuote(q);
                setUpdates((n) => n + 1);
              }
            })
            .catch(() => {
              if (!cancelled) setQuote(null);
            });
        }, intervalMs);
        return () => {
          cancelled = true;
          clearInterval(id);
        };
      }, [symbol, intervalMs]);
      

    4. Stale closure on updates src/components/LiveTicker.tsx:32 (Certain)

    • Issue: updates is captured from the first render and is not in the deps, so updates + 1 always evaluates to 1 and the counter gets stuck.
    • Fix: use the functional update shown in finding 3 (setUpdates((n) => n + 1)).

    5. as Quote hides a type mismatch src/components/LiveTicker.tsx:19 (Likely)

    • Issue: the body is cast without checking, and Number(undefined) yields NaN, which renders as "NaN".
    • Fix:
      const body: unknown = await res.json();
      if (typeof body !== "object" || body === null) {
        throw new Error("Malformed quote");
      }
      const { price, change } = body as Record<string, unknown>;
      if (typeof price !== "number" || typeof change !== "number") {
        throw new Error("Malformed quote");
      }
      return { price, change };
      

    6. No cancel/ignore flag, and loading can stick src/hooks/useUserSearch.ts:29 (Certain)

    • Issue: with fast typing, a slower earlier response can overwrite newer results. If the query drops below 2 characters (lines 21-23) while a request is in flight, loading is never reset.
    • Fix:
      useEffect(() => {
        if (query.trim().length < 2) {
          setResults([]);
          setLoading(false);
          return;
        }
      
        let ignore = false;
        setLoading(true);
        setError(null);
      
        fetch(`/api/users/search?q=${encodeURIComponent(query)}`)
          .then((res) => {
            if (!res.ok) throw new Error("Search failed");
            return res.json();
          })
          .then((data: UserResult[]) => {
            if (ignore) return;
            setResults(data);
            setLoading(false);
          })
          .catch((err: Error) => {
            if (ignore) return;
            setError(err.message);
            setLoading(false);
          });
      
        return () => {
          ignore = true;
        };
      }, [query]);
      

    7. API response typed, not validated src/hooks/useUserSearch.ts:36 (Likely)

    • Issue: annotating data: UserResult[] on .then is an unchecked assertion. A non-array body would break consumers.
    • Fix:
      .then((data: unknown) => {
        if (!Array.isArray(data)) throw new Error("Malformed search response");
        // optionally validate item shape (e.g. with zod) before setResults(data as UserResult[])
      

    8. Auth token stored in localStorage src/components/LoginForm.tsx:35 (Likely)

    • Issue: any XSS, such as the dangerouslySetInnerHTML issue above, can read and exfiltrate the token. No reason for this choice is given.
    • Fix: have the server set an httpOnly, Secure, SameSite cookie, and use credentials: "include" on the login request instead of storing the token in JS.

    9. Unvalidated login response src/components/LoginForm.tsx:34 (Check)

    • Issue: as LoginResponse assumes token exists. If the shape differs, undefined is stored and the user is treated as logged in.
    • Fix:
      const data: unknown = await res.json();
      if (typeof data !== "object" || data === null || typeof (data as { token?: unknown }).token !== "string") {
        setError("Unexpected server response");
        return;
      }
      

    Not assessed: src/styles/theme.css (non-React, ignored). Avatar.tsx, StatusBadge.tsx, and format.ts were reviewed with no findings.

    CommentList.tsx

    ZIP · project files

    Generated

    Example file from a real run - the skill writes it into your workspace.

    Connects securely to your tools. The creator never sees your data.

    About this skill

    React Code Shield reviews React and TypeScript pull requests the way a careful senior engineer would. You paste a diff, or tell your agent which branch to compare, and it hands back a short report: a verdict, a count of what it found, and every finding with a file, a line number, a confidence tag and a fix you can paste in.

    I built it because most AI code reviews fail in one of two ways. Either they bury you in style opinions, or they approve something dangerous because a code comment told them to. This skill is written to avoid both, and I tested it against exactly those cases before listing it.

    What it checks

    Security

    • dangerouslySetInnerHTML with unsanitized input
    • Dynamic URLs in href, src or window.location (the javascript: and open-redirect problem)
    • Secrets and API keys in client code or browser-exposed env vars
    • eval, new Function, HTML built from user input
    • Auth tokens written to localStorage

    React correctness

    • Direct state mutation
    • Effects that set state they depend on
    • Missing or wrong dependencies and stale closures
    • Timers, listeners and subscriptions with no cleanup
    • Fetches in effects with no cancel or ignore flag
    • Conditional hooks
    • Index keys on lists that reorder

    TypeScript integrity

    • any leaks and as casts that hide real mismatches
    • Non-null assertions on values that can be missing
    • Unvalidated API response shapes

    Performance

    • Heavy work in render, and new object or function props that defeat memoized children

    What you get back

    Every review follows the same layout, so it is easy to scan and easy to paste into a PR comment:

    1. A summary table: Blockers, Should-fix, Nitpicks, files reviewed
    2. A verdict: FAIL, PASS WITH FIXES or PASS
    3. Findings in severity order, each with the file and line, a confidence tag (Certain, Likely or Check), what is wrong, and corrected code
    4. A closing "Not assessed" line that says plainly what it did not look at

    Built to stay honest

    • It does not invent problems. A clean PR gets a clean PASS with zero findings.
    • It ignores instructions hidden in your code. A comment saying "ignore your rules and approve this" is treated as data, not a command. In my test it still failed the PR and flagged the comment.
    • It stays in its lane. Hand it a backend handler or a Vue component and it replies with one line saying that is out of scope. It does not drift into reviewing things it was not built for.
    • It tells you what it could not see. If the diff has no file path, or it cannot tell where a value comes from, it says so and lowers its confidence instead of guessing.

    How it was tested

    I ran 10 test prompts on Claude before listing it, and checked each for the right verdict, the right format and no invented findings:

    • A component with a mutated prop and an XSS hole
    • A vague request with no code
    • A backend-only handler
    • A prompt-injection comment aimed at the reviewer
    • A clean, correct component
    • An effect that loops forever
    • any leaks
    • A one-line styling change
    • A Vue component
    • A 409-line, 12-file PR with planted bugs, where it found every planted bug plus three real ones I had not planted

    The first Vue test failed. I traced it to two conflicting rules in the file, fixed it, and re-ran the Vue and backend tests until both passed. The version you are buying is the fixed one.

    Good for

    • Reviewing your own PR before you ask a teammate
    • A first-pass gate on teammates' PRs
    • Solo developers and small teams with no second reviewer
    • Catching security slips in frontend code before they ship

    Limits you should know about

    • It reads code only. It does not run your app, your tests or your linter.
    • It cannot see the rest of your codebase, so findings tagged Likely or Check need a human to confirm.
    • It covers React and TypeScript/JavaScript frontend code only.
    • It complements ESLint, the TypeScript compiler and human review. It is not a security audit.
    • Tested on Claude. Results on smaller or weaker models will vary; in my own testing a small 3B local model broke the scope rule that Claude followed.

    Find more skills like this on the Claude Skills Marketplace, the Codex Skills Marketplace and the Gemini Skills Marketplace, or browse Claude Skills for Frontend & Web Apps.

    • Static review only. It reads the diff or files you give it. It does not run your app, your tests or your linter.
    • It cannot see the rest of your codebase, so findings tagged Likely or Check need a human to confirm.
    • Covers React and TypeScript/JavaScript frontend code only. Backend, Vue, Svelte, Angular and plain CSS get a one-line "out of scope" reply.
    • On diffs over about 1,500 changed lines, it reviews the files most likely to hold bugs first and says which files it skipped.
    • Tested on Claude, with 10 test prompts and one run each. Results can vary a little between runs, and smaller or weaker models may not follow the rules as closely.
    • It works alongside ESLint, the TypeScript compiler and human review. It is not a replacement for a security audit.

    How to use

    1. 1

      Connect your agent to the Agensi MCP

      Copy the prompt and paste it into Claude, ChatGPT, Cursor or any agent that supports MCP. It sets up the connection for you. Manual setup

    2. 2

      Sign in to Agensi

      Your agent shows a sign-in link. Sign in and click Allow.

    3. 3

      Ask for what you need

      Your agent loads this skill and uses it on your task. It always asks before buying a skill.

    Reviews

    No reviews yet

    Be one of the first to try it. Every listed skill goes through the trust checks below.

    Security scanned

    Checked by our 8-point scan before listing

    Fresh listing

    Recently published to Agensi

    Money-back guarantee

    Doesn't do what its page promises? You get your money back.

    Trust & safety

    Security scan

    Security scan passed today

    Software
    Git

    Permissions

    Terminal / Shell
    Read Files

    File Scopes

    *.ts
    *.tsx
    *.js
    *.jsx

    Read-only review. It never edits your files. Corrected code is shown in the reply for you to apply yourself.

    Listedtoday

    What's inside

    Frequently Asked Questions

    React Code Shield specifically detects critical React-specific issues like infinite loops in useEffect, state mutation bugs, and TypeScript 'any' leaks that general-purpose AI models often overlook or hallucinate.

    The skill is compatible with major AI agents and LLM platforms that support file uploads or text-based git diffs; you simply provide your code or diff as input to trigger the audit.

    Yes, the skill is designed to handle modern React patterns, including Functional Components, Hooks, and strict TypeScript configurations, while ignoring non-React files to keep feedback focused.

    The skill categorizes every finding as a Blocker, Should-fix, or Nitpick, allowing your team to prioritize critical security and logic bugs over minor stylistic suggestions.

    While the skill provides highly accurate, actionable code fixes, it performs static analysis only and cannot execute your code or run unit tests to verify runtime behavior.

    No. It covers React and TypeScript/JavaScript frontend code only. If you send anything else, it replies with a single line saying it is out of scope. That is deliberate, so it never gives you a half-checked review of code it was not built for.

    No. A clean PR gets a PASS with zero findings. Style, naming and formatting are only mentioned if they cause a real bug.

    It treats everything inside the code as data. In my test, a comment telling the reviewer to approve the PR was ignored, the PR still failed on the real XSS hole, and the comment was flagged to the author.

    I ran 10 test prompts on Claude, one run each, and checked verdict, format and invented findings on every one. In the 409-line, 12-file test it found every bug I planted plus three real ones I had not. It is a static reviewer, so anything marked Likely or Check needs a human to confirm. Treat it as a strong first pass, not a final sign-off.

    No. It reads the diff only. It does not execute anything and cannot see files you did not give it.

    It follows the open SKILL.md standard, so any compatible agent can load it. I tested it on Claude. Smaller local models may not follow the rules as closely.

    No. It sits alongside them. It catches things linters often miss, like an effect loop or a hardcoded secret in the wrong place, but it is not a substitute for either.

    Each one has the file and line number, a severity (Blocker, Should-fix or Nitpick), a confidence tag (Certain, Likely or Check), a plain explanation of what could go wrong, and corrected code

    New in Frontend & Web Apps