home / skills / alinaqi / claude-bootstrap / react-native

react-native skill

/skills/react-native

This skill helps you implement React Native patterns and platform-specific code with clean architecture and reusable components.

npx playbooks add skill alinaqi/claude-bootstrap --skill react-native

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.5 KB
---
name: react-native
description: React Native mobile patterns, platform-specific code
---

# React Native Skill

*Load with: base.md + typescript.md*

---

## Project Structure

```
project/
├── src/
│   ├── core/                   # Pure business logic (no React)
│   │   ├── types.ts
│   │   └── services/
│   ├── components/             # Reusable UI components
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   ├── Button.test.tsx
│   │   │   └── index.ts
│   │   └── index.ts            # Barrel export
│   ├── screens/                # Screen components
│   │   ├── Home/
│   │   │   ├── HomeScreen.tsx
│   │   │   ├── useHome.ts      # Screen-specific hook
│   │   │   └── index.ts
│   │   └── index.ts
│   ├── navigation/             # Navigation configuration
│   ├── hooks/                  # Shared custom hooks
│   ├── store/                  # State management
│   └── utils/                  # Utilities
├── __tests__/
├── android/
├── ios/
└── CLAUDE.md
```

---

## Component Patterns

### Functional Components Only
```typescript
// Good - simple, testable
interface ButtonProps {
  label: string;
  onPress: () => void;
  disabled?: boolean;
}

export function Button({ label, onPress, disabled = false }: ButtonProps): JSX.Element {
  return (
    <Pressable onPress={onPress} disabled={disabled}>
      <Text>{label}</Text>
    </Pressable>
  );
}
```

### Extract Logic to Hooks
```typescript
// useHome.ts - all logic here
export function useHome() {
  const [items, setItems] = useState<Item[]>([]);
  const [loading, setLoading] = useState(false);

  const refresh = useCallback(async () => {
    setLoading(true);
    const data = await fetchItems();
    setItems(data);
    setLoading(false);
  }, []);

  return { items, loading, refresh };
}

// HomeScreen.tsx - pure presentation
export function HomeScreen(): JSX.Element {
  const { items, loading, refresh } = useHome();
  
  return (
    <ItemList items={items} loading={loading} onRefresh={refresh} />
  );
}
```

### Props Interface Always Explicit
```typescript
// Always define props interface, even if simple
interface ItemCardProps {
  item: Item;
  onPress: (id: string) => void;
}

export function ItemCard({ item, onPress }: ItemCardProps): JSX.Element {
  ...
}
```

---

## State Management

### Local State First
```typescript
// Start with useState, escalate only when needed
const [value, setValue] = useState('');
```

### Zustand for Global State (if needed)
```typescript
// store/useAppStore.ts
import { create } from 'zustand';

interface AppState {
  user: User | null;
  setUser: (user: User | null) => void;
}

export const useAppStore = create<AppState>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
}));
```

### React Query for Server State
```typescript
// hooks/useItems.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

export function useItems() {
  return useQuery({
    queryKey: ['items'],
    queryFn: fetchItems,
  });
}

export function useCreateItem() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: createItem,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['items'] });
    },
  });
}
```

---

## Testing

### Component Testing with React Native Testing Library
```typescript
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';

describe('Button', () => {
  it('calls onPress when pressed', () => {
    const onPress = jest.fn();
    const { getByText } = render(<Button label="Click me" onPress={onPress} />);
    
    fireEvent.press(getByText('Click me'));
    
    expect(onPress).toHaveBeenCalledTimes(1);
  });

  it('does not call onPress when disabled', () => {
    const onPress = jest.fn();
    const { getByText } = render(<Button label="Click me" onPress={onPress} disabled />);
    
    fireEvent.press(getByText('Click me'));
    
    expect(onPress).not.toHaveBeenCalled();
  });
});
```

### Hook Testing
```typescript
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('increments counter', () => {
    const { result } = renderHook(() => useCounter());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
});
```

---

## Platform-Specific Code

### Use Platform.select Sparingly
```typescript
import { Platform } from 'react-native';

const styles = StyleSheet.create({
  shadow: Platform.select({
    ios: {
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 2 },
      shadowOpacity: 0.1,
    },
    android: {
      elevation: 2,
    },
  }),
});
```

### Separate Files for Complex Differences
```
Component/
├── Component.tsx          # Shared logic
├── Component.ios.tsx      # iOS-specific
├── Component.android.tsx  # Android-specific
└── index.ts
```

---

## React Native Anti-Patterns

- ❌ Inline styles - use StyleSheet.create
- ❌ Logic in render - extract to hooks
- ❌ Deep component nesting - flatten hierarchy
- ❌ Anonymous functions in props - use useCallback
- ❌ Index as key in lists - use stable IDs
- ❌ Direct state mutation - always use setter
- ❌ Mixing business logic with UI - keep core/ pure
- ❌ Ignoring TypeScript errors - fix them
- ❌ Large components - split into smaller pieces

Overview

This skill documents opinionated React Native patterns for building secure, testable, and platform-aware mobile apps. It defines a structured project layout, component and state management conventions, testing approaches, and guidance for handling platform-specific code. The focus is on TypeScript-first, functional components, and separating business logic from UI.

How this skill works

The skill inspects and prescribes project layout and coding patterns: pure core business logic under src/core, reusable UI components under src/components, screens with paired hooks under src/screens, and clear navigation, hooks, store, and utils folders. It recommends functional components, explicit props interfaces, extracting logic into custom hooks, local-first state with Zustand or React Query for escalation, and platform-specific file separation where needed.

When to use it

  • Initializing a new React Native TypeScript app with predictable structure and security-first thinking.
  • Refactoring an existing app to separate business logic from UI and improve testability.
  • Standardizing component patterns and state management across a team.
  • Adding platform-specific behavior while keeping a single shared API.
  • Implementing reliable server state and caching with React Query.

Best practices

  • Use functional components only and move component logic into hooks for testable presentation layers.
  • Always declare explicit props interfaces even for small components to keep typing consistent.
  • Start with local useState; escalate to Zustand for app state and React Query for server state.
  • Write unit and integration tests: component tests with testing-library and hook tests with renderHook.
  • Avoid inline styles, anonymous functions in props, deep nesting, and direct state mutation.
  • Split complex platform differences into .ios/.android files instead of frequent Platform.select usage.

Example use cases

  • Create a Home screen where useHome hook handles data fetching and HomeScreen renders ItemList.
  • Build a reusable Button component with tests that assert press behavior and disabled state.
  • Introduce global auth state with a small Zustand store and typed setters accessible across screens.
  • Use React Query for items list with a useItems hook and a mutation that invalidates queries on success.
  • Handle a complex native layout by creating Component.ios.tsx and Component.android.tsx while keeping shared logic in Component.tsx.

FAQ

When should I use Zustand vs React Query?

Use Zustand for client-side global state like user session or UI flags. Use React Query for server-derived state that benefits from caching, refetching, and mutations.

How do I test hooks that use async data?

Use renderHook plus act and mock network calls. For React Query hooks, wrap with QueryClientProvider and control cache/queryClient for deterministic tests.