| name | frontend-expert |
| description | Frontend development expert specializing in React 18+ with Vite, TypeScript, Tailwind CSS, React Query (for API data fetching), Zustand (for UI state management), and TradingView Advanced Charts Widget integration. Use when reviewing, debugging, or fixing frontend code issues including: (1) React component architecture problems, (2) TypeScript type errors, (3) Tailwind CSS styling issues, (4) React Query caching/fetching problems, (5) Zustand state management bugs, (6) TradingView widget integration issues, (7) Build/bundling errors with Vite. |
Frontend Expert
Expert reviewer for modern React frontend applications built with Vite, TypeScript, Tailwind CSS, React Query, Zustand, and TradingView widgets.
Review Process
- Identify the scope - Determine which files/components need review
- Check architecture - Verify component structure follows best practices
- Validate types - Ensure TypeScript types are correct and complete
- Review state management - Check React Query and Zustand usage patterns
- Inspect styling - Verify Tailwind CSS implementation
- Test integrations - Validate TradingView widget configuration
React 18+ Best Practices
Component Architecture
interface Props {
marketId: string;
onSelect: (id: string) => void;
}
export function MarketCard({ marketId, onSelect }: Props) {
const { data, isLoading } = useMarketData(marketId);
if (isLoading) return <Skeleton />;
return (
<div onClick={() => onSelect(marketId)}>
{/* Component JSX */}
</div>
);
}
Common Issues to Fix
| Issue | Solution |
|---|
Missing key prop in lists | Add unique key from data ID, not array index |
| useEffect with missing deps | Include all referenced values in dependency array |
| Inline functions in props | Memoize with useCallback when passed to memoized children |
| Complex state in useState | Split into multiple states or use useReducer |
| Prop drilling > 2 levels | Consider Zustand store or React Context |
TypeScript Patterns
Strict Type Definitions
interface Market {
id: string;
question: string;
outcomes: Outcome[];
volume: number;
liquidity: number;
}
type OrderSide = 'buy' | 'sell';
type MarketStatus = 'open' | 'closed' | 'resolved';
interface ListProps<T extends { id: string }> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
Fix Common Type Errors
const handleData = (data: any) => { ... }
const handleData = <T extends Record<string, unknown>>(data: T) => { ... }
const value = data!.property;
const value = data?.property ?? defaultValue;
Tailwind CSS Guidelines
Responsive Design Pattern
<div className="
flex flex-col gap-4
md:flex-row md:gap-6
lg:gap-8
">
<div className="
bg-white text-gray-900
dark:bg-gray-900 dark:text-white
">
Component Styling Patterns
import { cn } from '@/lib/utils';
interface ButtonProps {
variant: 'primary' | 'secondary';
disabled?: boolean;
}
function Button({ variant, disabled, children }: ButtonProps) {
return (
<button
className={cn(
'px-4 py-2 rounded-lg font-medium transition-colors',
variant === 'primary' && 'bg-blue-600 text-white hover:bg-blue-700',
variant === 'secondary' && 'bg-gray-200 text-gray-900 hover:bg-gray-300',
disabled && 'opacity-50 cursor-not-allowed'
)}
disabled={disabled}
>
{children}
</button>
);
}
React Query (TanStack Query) v5
Query Pattern
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
const marketKeys = {
all: ['markets'] as const,
detail: (id: string) => [...marketKeys.all, id] as const,
orderbook: (id: string) => [...marketKeys.detail(id), 'orderbook'] as const,
};
function useMarket(marketId: string) {
return useQuery({
queryKey: marketKeys.detail(marketId),
queryFn: () => fetchMarket(marketId),
staleTime: 30 * 1000,
gcTime: 5 * 60 * 1000,
});
}
Mutation Pattern
function usePlaceOrder() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (order: OrderPayload) => placeOrder(order),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: marketKeys.orderbook(variables.marketId)
});
},
onError: (error) => {
toast.error(`Order failed: ${error.message}`);
},
});
}
Common Issues to Fix
| Issue | Solution |
|---|
| Stale data after mutation | Call invalidateQueries with correct query key |
| Infinite refetching | Check refetchInterval config, ensure stable query keys |
| Memory leaks | Enable gcTime, remove unused queries |
| Race conditions | Use useQuery with enabled flag, not manual fetching |
Zustand State Management
Store Pattern
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
interface UIState {
sidebarOpen: boolean;
selectedMarketId: string | null;
theme: 'light' | 'dark';
toggleSidebar: () => void;
selectMarket: (id: string) => void;
setTheme: (theme: 'light' | 'dark') => void;
}
export const useUIStore = create<UIState>()(
devtools(
persist(
(set) => ({
sidebarOpen: true,
selectedMarketId: null,
theme: 'dark',
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
selectMarket: (id) => set({ selectedMarketId: id }),
setTheme: (theme) => set({ theme }),
}),
{ name: 'ui-store' }
)
)
);
Selector Pattern for Performance
const { sidebarOpen, theme } = useUIStore();
const sidebarOpen = useUIStore((s) => s.sidebarOpen);
const theme = useUIStore((s) => s.theme);
import { shallow } from 'zustand/shallow';
const { sidebarOpen, theme } = useUIStore(
(s) => ({ sidebarOpen: s.sidebarOpen, theme: s.theme }),
shallow
);
When to Use Zustand vs React Query
| Use Case | Tool |
|---|
| Server state (API data) | React Query |
| Client-only UI state | Zustand |
| User preferences | Zustand with persist |
| Form state | React Hook Form or local state |
| Authentication state | Zustand (synced with API) |
TradingView Advanced Charts Widget
Basic Integration
import { useEffect, useRef } from 'react';
interface TradingViewWidgetProps {
symbol: string;
theme?: 'light' | 'dark';
interval?: string;
}
export function TradingViewWidget({
symbol,
theme = 'dark',
interval = 'D'
}: TradingViewWidgetProps) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const script = document.createElement('script');
script.src = 'https://s3.tradingview.com/tv.js';
script.async = true;
script.onload = () => {
new (window as any).TradingView.widget({
container_id: containerRef.current!.id,
symbol: symbol,
interval: interval,
theme: theme,
style: '1',
locale: 'en',
toolbar_bg: '#f1f3f6',
enable_publishing: false,
allow_symbol_change: true,
autosize: true,
});
};
containerRef.current.appendChild(script);
return () => {
if (containerRef.current) {
containerRef.current.innerHTML = '';
}
};
}, [symbol, theme, interval]);
return (
<div
id={`tradingview_${symbol.replace('/', '_')}`}
ref={containerRef}
className="h-[500px] w-full"
/>
);
}
Advanced Widget Configuration
const polymarketChartConfig = {
theme: 'dark',
style: '3',
hide_top_toolbar: false,
hide_side_toolbar: true,
hide_legend: false,
backgroundColor: 'rgba(0, 0, 0, 0)',
gridColor: 'rgba(255, 255, 255, 0.1)',
enable_publishing: false,
withdateranges: true,
save_image: false,
studies: [
'Volume@tv-basicstudies',
],
};
Common Widget Issues
| Issue | Solution |
|---|
| Widget not loading | Ensure script loads before creating widget instance |
| Multiple widgets conflict | Use unique container_id per widget instance |
| Memory leaks | Clean up widget in useEffect return function |
| Symbol not found | Verify symbol format matches TradingView naming |
| Resize issues | Use autosize: true and ensure parent has defined dimensions |
Vite Build Configuration
Common vite.config.ts Setup
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@hooks': path.resolve(__dirname, './src/hooks'),
'@lib': path.resolve(__dirname, './src/lib'),
},
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: true,
},
});
Fix Common Build Errors
| Error | Solution |
|---|
| Module not found | Check path aliases in vite.config.ts and tsconfig.json |
| Type errors blocking build | Fix types or use // @ts-ignore sparingly |
| Large bundle size | Code split with React.lazy(), analyze with vite-bundle-analyzer |
| CSS not loading | Verify Tailwind config and PostCSS setup |
Review Checklist
Before completing a frontend review, verify: