Loading...
Loading...
Frontend development guidelines for React/TypeScript applications. Modern patterns including Suspense, lazy loading, useSuspenseQuery, file organization with features directory, styling best practices, routing, performance optimization, and TypeScript best practices. Use when creating components, pages, features, fetching data, styling, routing, or working with frontend code.
npx skill4agent add bbeierle12/skill-mcp-claude frontend-dev-guidelinessrc/
├── features/ # Feature-based organization
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── api/
│ │ └── index.ts
│ ├── dashboard/
│ └── settings/
├── components/ # Shared/common components
│ ├── ui/ # Base UI components
│ └── layout/ # Layout components
├── hooks/ # Shared hooks
├── utils/ # Utility functions
├── types/ # TypeScript types
├── api/ # API layer
└── styles/ # Global stylesinterface Props {
title: string;
onAction: (id: string) => void;
isLoading?: boolean;
}
export function MyComponent({ title, onAction, isLoading = false }: Props) {
// Component logic
return (
<div>
{isLoading ? <Spinner /> : <Content title={title} />}
</div>
);
}import { Suspense } from 'react';
function ParentComponent() {
return (
<Suspense fallback={<LoadingSkeleton />}>
<DataComponent />
</Suspense>
);
}
function DataComponent() {
// useSuspenseQuery automatically suspends
const { data } = useSuspenseQuery({
queryKey: ['items'],
queryFn: fetchItems,
});
return <ItemList items={data} />;
}import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./features/dashboard'));
const Settings = lazy(() => import('./features/settings'));
function App() {
return (
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}const [value, setValue] = useState<string>('');
const [items, setItems] = useState<Item[]>([]);// Queries
const { data, isLoading, error } = useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
});
// Mutations
const mutation = useMutation({
mutationFn: updateUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});// Simple global state with Zustand
const useStore = create<Store>((set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
}));<div className="flex items-center gap-4 p-4 bg-white rounded-lg shadow-md">
<span className="text-lg font-semibold text-gray-900">
{title}
</span>
</div>import styles from './Component.module.css';
<div className={styles.container}>
<span className={styles.title}>{title}</span>
</div>const Container = styled.div`
display: flex;
align-items: center;
padding: ${({ theme }) => theme.spacing.md};
`;// Memoize expensive computations
const expensiveValue = useMemo(() => {
return computeExpensiveValue(data);
}, [data]);
// Memoize callbacks
const handleClick = useCallback((id: string) => {
onAction(id);
}, [onAction]);
// Memoize components
const MemoizedComponent = memo(function Component({ data }: Props) {
return <div>{data.value}</div>;
});// Route-based splitting
const Page = lazy(() => import('./Page'));
// Component-based splitting
const HeavyComponent = lazy(() => import('./HeavyComponent'));import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div key={virtualItem.key} style={{
position: 'absolute',
top: virtualItem.start,
height: virtualItem.size,
}}>
{items[virtualItem.index].name}
</div>
))}
</div>
</div>
);
}// Props interfaces
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
onClick: () => void;
children: React.ReactNode;
}
// API response types
interface ApiResponse<T> {
data: T;
meta: {
page: number;
total: number;
};
}
// Utility types
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;
type UserKeys = keyof User;// Handle potentially null values
const user = useUser();
if (!user) {
return <NotFound />;
}
// Now TypeScript knows user is defined
return <UserProfile user={user} />;import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>import { render, screen, fireEvent } from '@testing-library/react';
describe('Button', () => {
it('calls onClick when clicked', () => {
const onClick = vi.fn();
render(<Button onClick={onClick}>Click me</Button>);
fireEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledOnce();
});
});import { renderHook, act } from '@testing-library/react';
describe('useCounter', () => {
it('increments count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});