React frontend patterns for Opik. Use when working in apps/opik-frontend, on components, state, or data fetching.
View on GitHubFebruary 5, 2026
Select agents to install to:
npx add-skill https://github.com/comet-ml/opik/blob/b983072e17410cb43fc0f47221349d7cdab02f11/.agents/skills/opik-frontend/SKILL.md -a claude-code --skill opik-frontendInstallation paths:
.claude/skills/opik-frontend/# Opik Frontend
## Architecture Decisions
- **Routing**: TanStack Router (file-based)
- **Data fetching**: TanStack Query (never raw fetch/useEffect)
- **State**: Zustand for global, React state for local
- **Components**: shadcn/ui + Radix UI base
- **Forms**: React Hook Form + Zod validation
## Critical Gotchas
### Never useEffect for Data Fetching
```typescript
// ❌ BAD
useEffect(() => {
fetch('/api/data').then(setData);
}, []);
// ✅ GOOD
const { data } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
});
```
### Selective Memoization
```typescript
// ✅ USE useMemo for: complex computations, large data transforms
const filtered = useMemo(() =>
data.filter(x => x.status === 'active').map(transform),
[data]
);
// ✅ USE useCallback for: functions passed to children
const handleClick = useCallback(() => doSomething(id), [id]);
// ❌ DON'T memoize: simple values, primitives, local functions
const name = data?.name ?? ''; // No useMemo needed
```
### Zustand Selectors
```typescript
// ✅ GOOD - specific selector
const selectedEntity = useEntityStore(state => state.selectedEntity);
// ❌ BAD - selecting entire store causes re-renders
const { selectedEntity, filters } = useEntityStore();
```
## Layer Architecture
```
ui → shared → pages-shared → pages (one-way only)
```
- No circular dependencies
- No cross-page imports
- After modifying imports: `npm run deps:validate`
## State Location Decisions
- **URL state**: filters, pagination, selected items
- **Zustand**: user preferences, cross-component state
- **React state**: form inputs, UI toggles
## Component Structure
```typescript
const Component: React.FC<Props> = ({ prop }) => {
// 1. State hooks
// 2. Queries/mutations
// 3. Memoization (only when needed)
// 4. Event handlers
if (isLoading) return <Loader />;
if (error) return <ErrorComponent />;
return <div>...</div>;
};
```
## Query Patterns
```typescript
// Query with params
const { data } = useQuery({
queryKey: [ENTITY_K