| name | frontend-engineer |
| description | Expert in web app frontend architecture, React patterns, TypeScript, TanStack Query, Zustand, and component development with strong type safety. Use when building UI components, implementing data fetching, managing state, writing forms, styling with Tailwind, testing React components, or integrating with backend APIs. |
Frontend Engineer
Expert knowledge of frontend patterns and architecture for React web apps.
Core Architecture
Technology Stack
- Framework: React 18 with TypeScript
- State Management:
- Server State: TanStack Query (React Query)
- Client State: Zustand with Immer middleware
- Styling: Tailwind CSS + lucide-react icons
- Forms: react-hook-form + Zod validation
- Testing: Vitest + React Testing Library + MSW v2
- HTTP Client: Custom fetch wrapper with credentials
Data Flow Architecture
Component (React)
|
State Decision:
โโโ Server Data -> TanStack Query Hook -> API Service -> fetch
| |
| Type-Safe Response (schema/api)
|
โโโ UI State -> Zustand Store (with Immer)
Pattern References (ALWAYS CHECK THESE FIRST)
| Pattern | Reference Path | Notes |
|---|
| Reusable Components | packages/components/src/ | ALWAYS CHECK FIRST |
| API Types | packages/schema/src/api/ | CHECK HERE FIRST |
| API Service Layer | packages/ui/src/services/api.ts | Type-safe API calls |
| Zustand Store | packages/ui/src/stores/ | State management w/ Immer |
| Component Tests | packages/ui/src/components/*.test.tsx | Testing patterns |
| Domain Types | packages/schema/src/types.ts | Shared type definitions |
Type Safety & Validation
No Magic Strings
Always use enums, constants, or Zod enums:
export const ROUTES = {
HOME: '/',
ITEMS: '/items',
ITEM_DETAIL: (id: string) => `/items/${id}`,
} as const;
const StatusSchema = z.enum(['pending', 'completed']);
type Status = z.infer<typeof StatusSchema>;
navigate(`/items/${id}`);
type Status = string;
Type Generation Rules
- Check
packages/schema/src/api/ first
- All request/response types in schema package
- Add Zod schemas for runtime validation
- Export from index for both server and client
Component Development
Component Decision Flow
Need Component?
|
1. Check packages/components/ first <- ALWAYS START HERE
โโโ Exists? -> Use it
โโโ Doesn't exist
|
2. Reusable across features?
โโโ Yes -> Create in packages/components/
โโโ No -> Create in packages/ui/src/components/
Component Pattern
interface ItemListProps {
onToggle: (id: string) => void;
onDelete: (id: string) => void;
}
export function ItemList({ onToggle, onDelete }: ItemListProps) {
const { items, isLoading, error } = useItemStore();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return (
<ul className="space-y-2">
{items.map((item) => (
<ListItem key={item.id} item={item} onToggle={onToggle} onDelete={onDelete} />
))}
</ul>
);
}
State Management
TanStack Query (Server State)
export function useItems() {
return useQuery({
queryKey: ['items'],
queryFn: () => api.getItems(),
staleTime: 5 * 60 * 1000,
});
}
Zustand Store (Client State)
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
interface AppState {
items: Item[];
isLoading: boolean;
error: string | null;
fetchItems: () => Promise<void>;
}
export const useAppStore = create<AppState>()(
immer((set) => ({
items: [],
isLoading: false,
error: null,
fetchItems: async () => {
set({ isLoading: true, error: null });
try {
const items = await api.getItems();
set({ items, isLoading: false });
} catch (error) {
set({ error: 'Failed to fetch', isLoading: false });
}
},
}))
);
Decision Tree
Server data with caching? -> TanStack Query
Shared UI state? -> Zustand Store
Local component state? -> useState
Form Handling
react-hook-form + Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
export function ItemForm({ onSubmit }: { onSubmit: (data: CreateItemInput) => void }) {
const form = useForm<CreateItemInput>({
resolver: zodResolver(createItemSchema),
defaultValues: { title: '', description: '' },
});
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
<input {...form.register('title')} placeholder="Title" />
{form.formState.errors.title && (
<span>{form.formState.errors.title.message}</span>
)}
<button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? 'Saving...' : 'Save'}
</button>
</form>
);
}
Testing Patterns
Priority Order
- Unit Tests First โ Test logic in isolation
- MSW Integration Tests โ Test API interactions
- E2E Tests Last โ Only for critical user flows
Component Test
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';
describe('ItemList', () => {
it('should render items', () => {
render(<ItemList items={mockItems} onToggle={vi.fn()} onDelete={vi.fn()} />);
expect(screen.getByText('Test Item')).toBeInTheDocument();
});
it('should call onToggle when clicked', async () => {
const user = userEvent.setup();
const onToggle = vi.fn();
render(<ItemList items={mockItems} onToggle={onToggle} onDelete={vi.fn()} />);
await user.click(screen.getAllByRole('checkbox')[0]);
expect(onToggle).toHaveBeenCalledWith('1');
});
});
Common Commands
pnpm dev
pnpm build
pnpm type-check
pnpm test:changed
pnpm test:unit
pnpm test
pnpm vitest run --no-coverage src/components/ItemList.test.tsx
Component Checklist