Way typescript react style
Re-usable skills for agents at Way
npx -y skills add way-platform/skills --skill way-typescript-react-styleAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Guide for writing idiomatic, high-quality TypeScript and React code. Use this skill when writing, refactoring, or reviewing TypeScript/React code to ensure adherence to established conventions, React rules, and performance best practices. Trigger proactively when writing React components, TypeScript types, or Next.js pages.
SKILL.md
13.2 KB, as published. Nobody here has run it
Way TypeScript & React Style
Way Specific Conventions
- Define reusable data/logic helpers as top-level pure functions (outside React components); keep component bodies focused on rendering and wiring
- Complex predicates for array methods must be separate named functions
- Prefer declarative over imperative — derive values during render rather than storing them in
let/useState; usemap/filter/reduceexpressions rather than loops that push into an array; use object/array spread to produce new values rather than mutating existing ones - Always use early returns —
if (isLoading) return <Spinner />;at the top, notlet contentwith if/else branches - Never use IIFEs — extract a named function instead: a top-level pure function for derived values, or a named inner function for JSX that closes over component state
- Avoid
ascasts; prefer narrowing, generics, or splitting code paths. Useasonly when interoperating with external/untyped APIs and add a short justification comment - Handle undefined args explicitly (no
?? 0for mandatory params) - Do NOT use
useMemooruseCallback— React Compiler handles memoization. (Project AGENTS.md may define narrow exceptions.) - STOP before writing
useEffect— most uses are wrong. Evaluate the decision tree in this skill first. Effects are only for synchronizing with external systems. - Data fetching hierarchy: Always use
@connectrpc/connect-queryhooks with generated*_connectquery.tsfiles. Only fall back to raw@tanstack/react-querywhen no generated file exists or the pattern cannot be expressed via connect-query. - Hydration: Never render server-side content that depends on client state (timestamps,
windowchecks)
STOP — Before Writing useEffect
Effects are an escape hatch for synchronizing with external systems — if no external system is involved, you almost certainly don't need one.
Decision tree
About to write useEffect?
│
├─ Responding to a user action (click, submit, drag)?
│ └─ Use an EVENT HANDLER — you know exactly what happened
│
├─ Deriving a value from props or state?
│ └─ COMPUTE DURING RENDER — const fullName = first + ' ' + last
│
├─ Need to reset state when a prop changes?
│ └─ Use the KEY PROP — <Profile key={userId} />
│
├─ Filtering/transforming data?
│ └─ COMPUTE DURING RENDER (useMemo if expensive)
│
├─ Notifying a parent of a state change?
│ └─ Call the callback IN THE SAME EVENT HANDLER
│
├─ Chaining multiple state updates?
│ └─ Calculate ALL next state IN THE EVENT HANDLER
│
└─ Synchronizing with a non-React system (DOM API, WebSocket, browser API)?
└─ This is the ONE case where useEffect is correct
└─ Always include cleanup. For subscriptions, prefer useSyncExternalStore.
The top anti-patterns
| Anti-pattern | What to do instead |
|---|---|
useState + useEffect to derive a value | Compute during render: const x = a + b |
useEffect to filter/transform data | Compute during render (or useMemo) |
useEffect to reset state on prop change | key prop on the component |
useEffect to respond to a click/submit | Put the logic in the event handler |
useEffect calling parent's onChange | Call onChange in the event handler |
| Chain of Effects triggering each other | Compute all state changes in one event handler |
useEffect fetching without cleanup | Add let ignore = false cleanup (or use useQuery) |
App init in useEffect([], ...) | Module-level let didInit = false guard |
For detailed examples with incorrect/correct code, see:
- useEffect Anti-Patterns — 9 common mistakes with fixes
- Better Alternatives —
useMemo,keyprop, lifting state,useSyncExternalStore, event handlers
STOP — Before Writing Data Fetching Hooks
The most common mistake is reaching for raw @tanstack/react-query hooks when generated @connectrpc/connect-query hooks should be used instead. This causes redundant boilerplate, breaks transport injection, and bypasses the project's auth/error-handling layer.
The hierarchy (follow in order)
Fetching data from a ConnectRPC service?
│
├─ Is there a generated *_connectquery.ts file for this method?
│ └─ YES → Use @connectrpc/connect-query hooks ← DEFAULT
│ import { useQuery, useMutation } from "@connectrpc/connect-query"
│ import { listThings } from "@/gen/proto/.../*_connectquery"
│ const { data } = useQuery(listThings, { orgPath }, { transport })
│
└─ NO generated file, or need a pattern connect-query can't express?
└─ Fall back to @tanstack/react-query directly
import { useQuery } from "@tanstack/react-query"
(custom queryFn, useInfiniteQuery with complex pagination, etc.)
connect-query patterns
// READ — useQuery
import { useQuery } from "@connectrpc/connect-query";
import { listThings } from "@/gen/proto/.../things_service-ThingsService_connectquery";
const transport = useClientConfig();
const { data, isLoading } = useQuery(listThings, { orgPath }, { transport });
// Conditional skip — use skipToken (NOT enabled: false)
import { skipToken } from "@tanstack/react-query";
const { data } = useQuery(listThings, orgPath ? { orgPath } : skipToken, { transport });
// WRITE — useMutation
import { useMutation } from "@connectrpc/connect-query";
const { mutate, isPending } = useMutation(deleteThing, {
transport,
onError: (error) => toastApiError(error, t("deleteFailed")),
});
// Cache invalidation after mutation
import { createConnectQueryKey } from "@connectrpc/connect-query";
const queryClient = useQueryClient();
queryClient.invalidateQueries({
queryKey: createConnectQueryKey({ schema: listThings, transport, cardinality: "finite" }),
});
When raw tanstack-query IS appropriate
- Non-RPC fetches (REST endpoints, third-party APIs)
- Complex multi-RPC
queryFnthat composes several calls imperatively useSuspenseQuery/useSuspenseInfiniteQueryif not yet supported by connect-query- When you need
queryOptions()helper for SSR prefetching patterns
For the full tanstack-query API reference see references/tanstack-query.md.
TypeScript Style
Types
- Never use boxed types (
Number,String,Boolean,Symbol,Object). Use the lowercase primitives (number,string,boolean,symbol,object). - Never use
anyunless migrating from JavaScript. Useunknownfor truly unknown values. - Never have unused type parameters in generics.
Callbacks
- Return
void, notany, for callbacks whose return value is ignored.voidprevents accidental use of the return value. - Don't make callback parameters optional — callers can always pass a function that accepts fewer arguments.
- Don't overload on callback arity — write a single overload with the maximum arity.
Type Safety in Practice
- Narrow, don't cast — use type guards, discriminated unions, and
satisfiesinstead ofas. - Prefer
unknownoverany— force explicit narrowing at the call site. - Use
satisfiesto validate a value matches a type without widening it:const config = { ... } satisfies Config. - Prefer
readonlyfor data that shouldn't be mutated (function parameters, returned arrays). - Avoid enums in application code — use
as constobjects or string literal unions. (Generated proto enums are the exception.)
React Rules
These are rules, not guidelines — breaking them causes bugs. Condensed from the official Rules of React.
1. Components & Hooks Must Be Pure
- Idempotent: Same inputs (props, state, context) must produce the same output. Don't use
new Date()orMath.random()during render — move them to effects or event handlers. - No side effects in render: React can render components multiple times. Side effects belong in event handlers or
useEffect. - Local mutation is fine: Creating and modifying local variables during render is OK — the mutation isn't remembered between renders.
- Props, state, and hook return values are immutable: Never mutate them directly. Use setter functions.
- Don't mutate values after passing to JSX: React may evaluate JSX eagerly; mutating after the fact leads to stale UI.
2. React Controls Invocation
- Never call component functions directly — use them in JSX. React needs to orchestrate rendering, reconciliation, and state management.
- Never pass hooks around as values — always call hooks inline. Don't dynamically mutate or inject hooks.
3. Rules of Hooks
- Top level only — no hooks in loops, conditions, nested functions, try/catch, or after early returns.
- React functions only — call hooks from function components or custom hooks, never from regular functions.
React & Next.js Performance
See references/vercel-rules/ for the full 57-rule reference. Key patterns:
Eliminating Waterfalls
- Move
awaitinto the branch that uses it — don't block early returns with unneeded fetches Promise.all()for independent operations; start promises early, await late- Wrap slow data components in
<Suspense>so the surrounding layout renders immediately - Sibling async Server Components fetch in parallel; a parent that
awaits before rendering children creates a waterfall
Bundle Size
- Import directly from source files, not barrel
index.tsre-exports next/dynamicfor heavy components (maps, editors, charts)- Defer analytics/logging with
dynamic(() => ..., { ssr: false }) - Trigger
import()on hover/focus to preload before click
Server-Side Performance
- Verify auth inside every Server Action — don't rely on middleware alone
- Pass only the fields a client component actually uses (no 50-field objects)
- Sibling async Server Components fetch in parallel; awaiting in a parent creates a waterfall
React.cache()deduplicates calls within one request (avoid inline object args)after()for logging/analytics so it doesn't block the response
Re-render Optimization
- Derive values during render — don't store computed state or sync via effects
setState(prev => ...)when new state depends on previous valueuseState(() => expensiveInit())— function form, not expression- Depend on
user.idnotuser; derive booleans and depend on those useReffor transient values (mouse position, timers, flags)startTransitionfor non-urgent updates
Rendering Performance
- Explicit conditional rendering: Use ternary (
condition ? <A /> : null), not&&, when the condition could be0orNaN. - Animate SVG wrappers: Wrap SVG in a
<div>and animate the wrapper for GPU acceleration. content-visibility: auto: Defer off-screen rendering for long lists.- Hydration mismatches: Use
suppressHydrationWarningonly for expected differences (timestamps, locales). For client-only data (theme), use an inline<script>to set the DOM before hydration.
JavaScript Performance
Set/Mapfor repeated lookups instead of array scanstoSorted()/toReversed()/toSpliced()—.sort()mutates- Combine multiple
.filter()/.map()passes into one loop - Hoist
RegExpto module scope; cachelocalStoragereads in a module-levelMap
Advanced Patterns
- Initialize once per app load: Don't put one-time init in
useEffect([], ...)— it re-runs on remount and runs twice in dev. Use a module-levellet didInit = falseguard. useEffectEvent: Creates a stable function reference that always calls the latest handler, without adding the handler to effect dependencies.
Available References
Detailed documentation available in the references/ directory:
- useEffect Anti-Patterns: 9 common mistakes with incorrect/correct code examples.
- useEffect Alternatives:
useMemo,keyprop, lifting state,useSyncExternalStore, event handlers, custom hooks. - TypeScript Do's and Don'ts: Official TypeScript handbook guidance on types, callbacks, and overloads.
- Rules of React: All four pages of React's official rules — purity, invocation, hooks.
- Vercel React Rules: 57 individual rule files with incorrect/correct code examples. Named by category prefix:
async-*,bundle-*,server-*,client-*,rerender-*,rendering-*,js-*,advanced-*. Load individual files on demand rather than the full set. - TanStack Query Reference: Full API reference for
@tanstack/react-queryv5 — for fallback cases where connect-query is insufficient.