Apply this skill when writing, reviewing, or refactoring any code in a React + TypeScript + Tailwind + shadcn + Drizzle project. Triggers on requests like "review this code", "refactor this component", "is this good practice", "write a hook for", "clean this up", "add types to", "write a form", or any time you are producing code that should meet production standards. Use proactively — all generated code should conform to these standards without being asked.
87
Quality
87%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
any — Use unknown + Type Guardsfunction processUser(user: unknown): User {
if (!isUser(user)) throw new Error('Invalid user shape')
return user
}
function isUser(value: unknown): value is User {
return typeof value === 'object' && value !== null && 'id' in value && 'email' in value
}// ❌ Lying to the compiler — blows up at runtime
const user = JSON.parse(body) as User
// ✅ Validate at runtime with Zod
const user = UserSchema.parse(JSON.parse(body))// ✅ Single source of truth
import { users } from '@/lib/db/schema/users'
type User = typeof users.$inferSelect
type NewUser = typeof users.$inferInsert
// ❌ Duplicating types manually
interface User { id: string; email: string; ... }// ✅ String literal unions — better inference, simpler serialisation
type Status = 'loading' | 'success' | 'error'
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
// ❌ Enums have awkward runtime behaviour and don't serialise cleanly
enum Status { Loading = 'loading', Success = 'success' }// ✅ Refactors that change the shape are caught immediately
async function getUser(id: string): Promise<User | undefined> {
return db.query.users.findFirst({ where: eq(users.id, id) })
}If a teammate can't read a type without mentally executing the type system, simplify it.
== null for Nullish Checksvalue == null is the idiomatic way to check for both null and undefined. Use === everywhere else.
interface UserCardProps {
user: User
onEdit?: (id: string) => void
className?: string
}
export function UserCard({ user, onEdit, className }: UserCardProps) {
return (
<div className={cn('rounded-lg border p-4', className)}>
<p className="font-medium">{user.name}</p>
{onEdit && (
<Button variant="ghost" size="sm" onClick={() => onEdit(user.id)}>Edit</Button>
)}
</div>
)
}export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: () => fetchUsers(),
staleTime: 1000 * 60 * 5,
})
}
export function useCreateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: UserInsert) => createUser(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
})
}Inline definitions get a new reference every render, causing full remounts.
Keys must be stable, unique identifiers — use item.id, not index.
Always use this combination. Never manage form state manually with useState. Always use shadcn FormField/FormItem/FormMessage — they handle aria-invalid, aria-describedby, etc.
const schema = z.object({
email: z.string().email('Invalid email'),
name: z.string().min(1, 'Name is required').max(255),
})
type FormValues = z.infer<typeof schema>
export function UserForm({ onSubmit }: { onSubmit: (data: FormValues) => void }) {
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: { email: '', name: '' },
})
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl><Input placeholder="you@example.com" {...field} /></FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? 'Saving...' : 'Save'}
</Button>
</form>
</Form>
)
}cn() for Conditional Classes<div className={cn('rounded-lg border p-4', isActive && 'border-primary bg-primary/5', className)}>
// ❌ String concatenation breaks Tailwind purging
<div className={`rounded-lg ${isActive ? 'border-primary' : ''}`}>cva() for Variant APIsconst badge = cva('inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium', {
variants: {
variant: {
default: 'bg-primary text-primary-foreground',
secondary: 'bg-secondary text-secondary-foreground',
destructive: 'bg-destructive text-destructive-foreground',
},
},
defaultVariants: { variant: 'default' },
})sm:, md:, lg: prefixes always go largerw-[347px] unless genuinely necessary// ✅ Efficient — only fetch needed columns
const result = await db
.select({ id: users.id, email: users.email })
.from(users)
.where(eq(users.active, true))
// ❌ Fetches all columns
const result = await db.select().from(users)await db.transaction(async (tx) => {
const [order] = await tx.insert(orders).values(orderData).returning()
await tx.insert(orderItems).values(items.map(i => ({ ...i, orderId: order.id })))
})const usersWithPosts = await db.query.users.findMany({
with: { posts: true },
where: eq(users.active, true),
})Left join results can be null — type and handle them explicitly.
export const users = pgTable('users', {
updatedAt: timestamp('updated_at').$onUpdateFn(() => new Date()),
})Always use drizzle-kit generate and drizzle-kit migrate. Never use drizzle-kit push in production.
You're using TanStack Query. A bare useEffect fetch is always wrong.
If a value can be calculated from props or state, calculate it — don't store it.
// ❌ Extra render cycle
useEffect(() => { setFullName(`${first} ${last}`) }, [first, last])
// ✅ Plain variable or useMemo
const fullName = `${first} ${last}`
const sortedList = useMemo(() => [...items].sort(), [items])If something happens because of a user action, handle it in the event handler — not an effect.
Any effect that subscribes must return a cleanup function.
Never suppress exhaustive-deps. If adding a dependency causes a loop, the architecture is wrong — fix that instead.
TanStack Query IS your state. Don't copy it into useState or Redux.
// ❌ Double state — cache and useState diverge
const { data } = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
useEffect(() => { if (data) setUsers(data) }, [data])
// ✅ Use query result directly
const { data: users, isLoading, isError } = useQuery({ queryKey: ['users'], queryFn: fetchUsers })if (isLoading) return <Skeleton />
if (isError) return <ErrorMessage />
return <UserList users={data} />onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] })export const userQueryOptions = (id: string) => queryOptions({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
staleTime: 1000 * 60 * 5,
})const { data: activeUsers } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
select: (users) => users.filter(u => u.active),
})UI state belongs in useState, Zustand, or Context.
type ActionResult<T> =
| { success: true; data: T }
| { success: false; error: string }
export async function createUser(input: unknown): Promise<ActionResult<User>> {
const parsed = UserInsertSchema.safeParse(input)
if (!parsed.success) return { success: false, error: parsed.error.issues[0].message }
try {
const [user] = await db.insert(users).values(parsed.data).returning()
return { success: true, data: user }
} catch (error) {
console.error('createUser failed', error)
return { success: false, error: 'Failed to create user' }
}
}<Button onClick={handleSubmit} disabled={mutation.isPending}>
{mutation.isPending ? 'Saving...' : 'Save'}
</Button>Wrap route-level components in an ErrorBoundary. Use shadcn Alert for inline errors.
Install with Tessl CLI
npx tessl i product-factory/code-quality