Skip to content
Back to skills

Hamster React

ASecurity

Hamster-specific React and Next.js patterns. Use when creating pages, components, forms, server actions, or data fetching in apps/web. Complements the react-best-practices skill.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
developmentgoreactnextjsgitapiperformance

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 9 files and shows the line behind each finding

Scanned September 27, 2026

npx -y skills add gethamster/skills --skill hamster-react --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hamster React?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hamster React
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/gethamster-hamster-react/badge)](https://www.skillsdirectory.com/skills/gethamster-hamster-react)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: hamster-react
description: "Hamster-specific React and Next.js patterns. Use when creating pages, components, forms, server actions, or data fetching in apps/web. Complements the react-best-practices skill."
version: "1.1.0"
---

# Hamster React Patterns

## Overview

React and Next.js conventions specific to the Hamster Studio monorepo. Covers page creation, server actions, data fetching, forms, and UI components. For general React performance patterns, see the `react-best-practices` skill.

## When to Apply

- Creating new pages or layouts in apps/web
- Writing server actions or route handlers
- Building forms with validation
- Fetching data in server or client components
- Using @kit/ui components

## Quick Reference

| Task | Pattern | Reference |
| --- | --- | --- |
| Create page | `withI18n`, `generateMetadata`, `PageBody` | `references/rules/page-creation.md` |
| Server action | `enhanceAction` with schema, `'use server'` | `references/rules/server-actions.md` |
| Fetch data | Server: direct Supabase, Client: React Query | `references/rules/data-fetching.md` |
| Build form | react-hook-form + Zod + server action | `references/rules/forms.md` |
| UI components | Import from `@kit/ui/*` | `references/rules/ui-components.md` |

## Architecture Overview

```
app/home/
├── (user)/           # Personal account context
│   ├── _components/  # Route-specific components
│   ├── _lib/server/  # Loaders, actions
│   └── page.tsx
└── [account]/        # Team account context (slug, not UUID)
    ├── _components/
    ├── _lib/server/
    └── page.tsx
```

## Key Patterns

### Page Structure

```tsx
import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
import { withI18n } from '~/lib/i18n/with-i18n';

export const generateMetadata = async () => {
  const i18n = await createI18nServerInstance();
  return { title: i18n.t('feature:pageTitle') };
};

function FeaturePage() {
  return (
    <>
      <FeatureHeader title={<Trans i18nKey="common:routes.feature" />} />
      <PageBody>{/* Content */}</PageBody>
    </>
  );
}

export default withI18n(FeaturePage);
```

### Server Action Pattern

```tsx
'use server';

import { enhanceAction } from '@kit/next/actions';
import { CreateItemSchema } from '../schema/create-item.schema';

export const createItemAction = enhanceAction(
  async (data, user) => {
    // data validated, user authenticated
    const client = getSupabaseServerClient();
    const { data: item } = await client.from('items').insert(data).select().single();
    return { success: true, item };
  },
  { auth: true, schema: CreateItemSchema }
);
```

### Data Fetching Decision

| Context | Use | Example |
| --- | --- | --- |
| Server Component | Direct Supabase | `await client.from('items').select()` |
| Client Component | React Query + `useSupabase` | `useQuery({ queryFn: ... })` |
| Admin bypass | `getSupabaseServerAdminClient` | Rare, requires manual auth |

### Form Pattern

```tsx
'use client';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Form, FormField, FormItem, FormControl, FormMessage } from '@kit/ui/form';
import { toast } from '@kit/ui/sonner';

function MyForm() {
  const form = useForm({ resolver: zodResolver(Schema) });

  const onSubmit = (data) => {
    startTransition(async () => {
      await toast.promise(myAction(data), {
        loading: t('creating'),
        success: t('created'),
        error: t('error')
      });
    });
  };

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        {/* FormFields */}
      </form>
    </Form>
  );
}
```

## useEffect Rules (CRITICAL)

**MUST** follow [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect). Before writing any `useEffect`, ask: _"Is this synchronizing with an external system?"_ If not, you probably don't need it.

### When useEffect is WRONG

#### 1. Derived state — compute during render instead

```tsx
// 🔴 WRONG: redundant state + unnecessary Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
  setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);

// ✅ CORRECT: calculate during render
const fullName = firstName + ' ' + lastName;

// ✅ CORRECT: memoize if expensive
const filtered = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
```

#### 2. State reset on prop change — use `key` or render-time adjustment

```tsx
// 🔴 WRONG: resetting state in an Effect
useEffect(() => {
  setComment('');
}, [userId]);

// ✅ BEST: use key to reset entire component
<Profile userId={userId} key={userId} />

// ✅ OK: render-time adjustment when only partial reset needed
const [prevItems, setPrevItems] = useState(items);
if (items !== prevItems) {
  setPrevItems(items);
  setSelection(null);
}

// ✅ BEST: derive instead of resetting
const selection = items.find(item => item.id === selectedId) ?? null;
```

#### 3. Event-specific logic — put it in the event handler

```tsx
// 🔴 WRONG: event logic in an Effect
useEffect(() => {
  if (product.isInCart) {
    showNotification(`Added ${product.name}!`);
  }
}, [product]);

// ✅ CORRECT: call from the event handler
function handleBuyClick() {
  addToCart(product);
  showNotification(`Added ${product.name}!`);
}
```

#### 4. Notifying parent about state changes — call in the event handler

```tsx
// 🔴 WRONG: notifying parent via Effect
useEffect(() => {
  onChange(isOn);
}, [isOn, onChange]);

// ✅ CORRECT: notify during the event that caused the change
function updateToggle(nextIsOn) {
  setIsOn(nextIsOn);
  onChange(nextIsOn);
}
```

#### 5. Passing data to parent — lift the fetch up instead

```tsx
// 🔴 WRONG: child fetches, passes to parent via Effect
function Child({ onFetched }) {
  const data = useSomeAPI();
  useEffect(() => {
    if (data) onFetched(data);
  }, [data, onFetched]);
}

// ✅ CORRECT: parent fetches, passes down
function Parent() {
  const data = useSomeAPI();
  return <Child data={data} />;
}
```

#### 6. Effect chains — compute in render, update in event handlers

```tsx
// 🔴 WRONG: chain of Effects triggering each other
useEffect(() => { if (card?.gold) setGoldCount(c => c + 1); }, [card]);
useEffect(() => { if (goldCount > 3) { setRound(r => r + 1); setGoldCount(0); } }, [goldCount]);
useEffect(() => { if (round > 5) setGameOver(true); }, [round]);

// ✅ CORRECT: derive what you can, update in event handler
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
  setCard(nextCard);
  if (nextCard.gold) {
    if (goldCount < 3) setGoldCount(goldCount + 1);
    else { setGoldCount(0); setRound(round + 1); }
  }
}
```

### When useEffect IS correct

| Pattern | Why it's correct |
| --- | --- |
| Subscriptions with cleanup | Synchronizing with external system (realtime channels, WebSocket, event listeners) |
| Data fetching with cleanup | Synchronizing with network (add `ignore` flag for race conditions) |
| DOM manipulation via refs | Synchronizing with browser DOM (focus, scroll, ResizeObserver) |
| Analytics on mount | Side effect because component was _displayed_ |
| Ref syncs for stale closures | Keeping refs current for callbacks used in subscriptions |
| External store mutations | Synchronizing React state with Zustand/external stores |

### Key principle

> **"If this logic is caused by a particular interaction, keep it in the event handler. If it's caused by the user _seeing_ the component on the screen, keep it in the Effect."**

### Lint enforcement

The codebase enforces these patterns via:

- `react-hooks/set-state-in-effect` — flags synchronous setState inside effects (use `useEffectEvent` wrapper for legitimate cases like hydration detection)
- `react-hooks/refs` — flags ref access during render (keep ref syncs in `useEffect`, not render body)
- Consider a community “you might not need an effect” lint plugin for broader detection of redundant effects

## Component Guidelines

### Server vs Client Components

| Use Server Components | Use Client Components |
| --- | --- |
| Data fetching | Interactivity (onClick, onChange) |
| Authenticated queries | Browser APIs |
| Static rendering | React hooks (useState, useEffect) |
| No hooks needed | Real-time listeners |

### Workspace Contexts

```tsx
// Personal account
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';
const { user, account } = useUserWorkspace();

// Team account
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
const { account, user, accounts } = useTeamAccountWorkspace();
```

### JSX Patterns

```tsx
// Class merging
import { cn } from '@kit/ui/utils';
<div className={cn('base', { 'active': isActive }, className)} />

// Conditional rendering
import { If } from '@kit/ui/if';
<If condition={isLoading} fallback={<Content />}>
  <Spinner />
</If>

// Translations (no hardcoded strings)
import { Trans } from '@kit/ui/trans';
<Trans i18nKey="feature:message" values={{ name }} />
```

## File Organization

| File | Purpose |
| --- | --- |
| `page.tsx` | Route page component |
| `layout.tsx` | Shared layout |
| `loading.tsx` | Loading state |
| `error.tsx` | Error boundary |
| `_components/` | Route-specific components |
| `_lib/server/` | Loaders, actions, queries |
| `_lib/schema/` | Zod validation schemas |

## References

- `references/rules/page-creation.md` - Creating pages and layouts
- `references/rules/server-actions.md` - Server action patterns
- `references/rules/data-fetching.md` - Data fetching strategies
- `references/rules/forms.md` - Form implementation
- `references/rules/ui-components.md` - UI component usage
- `references/rules/react-patterns.md` - React component patterns
- `references/rules/translations.md` - i18n implementation

Files in this skill

  • SKILL.md9.7 KB
  • references/rules/cross-browser-events.md3.3 KB
  • references/rules/data-fetching.md4.5 KB
  • references/rules/forms.md6.1 KB
  • references/rules/page-creation.md4.6 KB
  • references/rules/react-patterns.md5 KB
  • references/rules/server-actions.md3.8 KB
  • references/rules/translations.md3.6 KB
  • references/rules/ui-components.md4.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…