ワンクリックで
react-native
React Native and Expo patterns, platform-specific code, list performance, animations, and navigation best practices
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
React Native and Expo patterns, platform-specific code, list performance, animations, and navigation best practices
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Automated project implementation orchestrator that drives feature-driven development from a single initial prompt through to completed code. Use this skill when the user invokes /init-project, /init-features, /add-feature, /continue-tasks, /continue-new-session, /iterate-tasks, /review-tasks, /update-tasks, /analyze-features, or /reinit. Also trigger proactively when docs/INITIAL_PROMPT.md exists and the user says anything like "move forward", "keep building", "what's next", "continue the implementation", or "start working on the project", AND when the user says "set up project management", "bootstrap a new project", "initialize the project workflow", or "make sure agents follow the project plan". This skill manages the full lifecycle: scaffolding a new project with enforcement artifacts, extracting feature specs via interview, generating phased implementation plans, spawning typed agents to execute tasks, monitoring completion sentinels, recovering from failures, and archiving finished work — all driven by m
Generate a copy-ready prompt that a fresh session (Claude Code, Codex, or Gemini) can paste in to immediately begin work on the next recommended action that was identified in this session's most recent recap. Reference-style — names file paths with short excerpts, no large inline content.
Self-perpetuating, subagent-driven iteration of the project pipeline. One invocation: (1) merges any pending PR left by the previous turn, (2) dispatches the recap-recommended next action as a fresh subagent so each unit of work gets a clean context, then (3) emits a copy-ready prompt for the action that comes AFTER the one just completed. Designed to pair with /loop for unattended runs and to minimize parent-context growth.
Run the full project orchestration loop with approved-spec gating, dependency ordering, verified completion, and deterministic failure/blocked handling
Use when starting a new project from scratch or retrofitting the project-manager workflow onto an existing repo. Scaffolds the docs/ tree, copies canonical templates, installs agent-enforcement artifacts (AGENTS.md, CLAUDE.md fragment, pre-commit guard, Claude Code hook, PR template, ROADMAP), and seeds INITIAL_PROMPT.md. Idempotent: re-running only fills gaps.
Dry-run project status report. Produces a read-only snapshot of feature specs, dependency blockers, plans, active tasks, stale work, verification backlog, and open issues without spawning agents or modifying files.
| name | react-native |
| description | React Native and Expo patterns, platform-specific code, list performance, animations, and navigation best practices |
Load with: base.md + typescript.md
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
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>
);
}
// 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} />;
}
interface ItemCardProps {
item: Item;
onPress: (id: string) => void;
}
export function ItemCard({ item, onPress }: ItemCardProps): JSX.Element {
// ...
}
&&// Bad — renders "0" when count is 0
{count && <Badge count={count} />}
// Good — always explicit
{count > 0 && <Badge count={count} />}
{isVisible ? <Modal /> : null}
// Start with useState, escalate only when needed
const [value, setValue] = useState('');
Subscribe to the smallest slice of state possible — avoid subscribing to entire store objects when you only need one field.
// 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 }),
}));
export function useItems() {
return useQuery({ queryKey: ['items'], queryFn: fetchItems });
}
export function useCreateItem() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createItem,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['items'] }),
});
}
When data is loading, always show a skeleton or fallback — never render empty containers that cause layout shift.
High-performance lists are the most common React Native performance bottleneck.
FlashList (from @shopify/flash-list) recycles cells and is dramatically faster than FlatList for lists with many items.
import { FlashList } from '@shopify/flash-list';
<FlashList
data={items}
renderItem={({ item }) => <ItemCard item={item} />}
estimatedItemSize={80}
keyExtractor={(item) => item.id}
/>
// Good — component only re-renders when its props change
const ItemCard = React.memo(({ item, onPress }: ItemCardProps) => (
<Pressable onPress={() => onPress(item.id)}>
<Text>{item.title}</Text>
</Pressable>
));
// Bad — new function on every render causes all items to re-render
<FlashList renderItem={({ item }) => <ItemCard onPress={(id) => handlePress(id)} />} />
// Good — stable reference via useCallback
const handlePress = useCallback((id: string) => {
// ...
}, []);
<FlashList renderItem={({ item }) => <ItemCard onPress={handlePress} />} />
// Bad — new object reference on every render
<ItemCard style={{ padding: 8 }} />
// Good — stable reference
const styles = StyleSheet.create({ card: { padding: 8 } });
<ItemCard style={styles.card} />
When a list contains different item shapes, use getItemType to let FlashList reuse the correct cell recycling pool.
<FlashList
getItemType={(item) => item.type} // 'header' | 'row' | 'footer'
renderItem={({ item }) => {
if (item.type === 'header') return <SectionHeader />;
return <ItemRow item={item} />;
}}
/>
Use expo-image (not Image from react-native) — it supports memory/disk caching, blurhash placeholders, and is significantly more performant in lists.
import { Image } from 'expo-image';
<Image source={{ uri: item.imageUrl }} style={styles.thumbnail} contentFit="cover" />
transform and opacityGPU-accelerated properties only. Never animate width, height, top, left, margin, or padding — these trigger layout recalculation on the JS thread.
// Good — GPU properties
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }, { translateY: translateY.value }],
opacity: opacity.value,
}));
useDerivedValue for Computed Animations// Good — computed on the UI thread, no JS bridge overhead
const rotation = useDerivedValue(() => `${progress.value * 360}deg`);
Gesture.Tap over Pressable in Animated ContextsWhen paired with Reanimated, Gesture.Tap from react-native-gesture-handler avoids the JS bridge entirely:
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
const tap = Gesture.Tap().onEnd(() => {
scale.value = withSpring(1);
});
<GestureDetector gesture={tap}>
<Animated.View style={animatedStyle} />
</GestureDetector>
Prefer @react-navigation/native-stack and @react-navigation/bottom-tabs over the JS-based equivalents. Native navigators use platform navigation components and are hardware-accelerated.
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
expo-imageimport { Image } from 'expo-image';
// Supports blurhash placeholders, caching, content-fit
<Image source={uri} placeholder={blurhash} contentFit="cover" />
Pressable is the current recommended primitive — it supports pressed state, hit slop, and is more composable.
<Pressable
onPress={onPress}
style={({ pressed }) => [styles.button, pressed && styles.buttonPressed]}
hitSlop={8}
>
<Text>Press me</Text>
</Pressable>
import { useSafeAreaInsets } from 'react-native-safe-area-context';
export function Screen() {
const insets = useSafeAreaInsets();
return (
<ScrollView contentContainerStyle={{ paddingBottom: insets.bottom }}>
{/* content */}
</ScrollView>
);
}
Use Modal from react-native or platform-specific sheet libraries over JS-based modal stacks — native modals participate in the OS accessibility and gesture systems.
onLayout// Good — layout callback, no layout thrash
<View onLayout={(e) => setHeight(e.nativeEvent.layout.height)} />
// Bad — synchronous measure() forces a layout pass
ref.current?.measure((x, y, w, h) => setHeight(h));
Use StyleSheet.create for static styles, or Nativewind for Tailwind-style utility classes. Never use inline style objects in render paths that re-render frequently.
Platform.select for Minor Differencesimport { 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 },
}),
});
Component/
├── Component.tsx # Shared logic
├── Component.ios.tsx # iOS-specific
├── Component.android.tsx # Android-specific
└── index.ts
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();
});
});
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';
it('increments counter', () => {
const { result } = renderHook(() => useCounter());
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
Native modules (those with android/ or ios/ directories) must live in the app package, not in shared packages. Metro can't hoist native modules.
Use a single version of React Native and its ecosystem packages across all packages in the monorepo. Version mismatches cause subtle runtime errors.
// app.config.ts
export default {
plugins: [
['expo-font', { fonts: ['./assets/fonts/Inter.ttf'] }]
]
};
// Good — explicit, tree-shakeable
import { Button } from '@company/design-system/button';
// Avoid — barrel imports can include large unused chunks
import { Button } from '@company/design-system';
| ❌ Avoid | ✅ Instead |
|---|---|
| Inline styles in hot paths | StyleSheet.create or Nativewind |
| Logic in render functions | Extract to hooks |
| Deep component nesting | Flatten hierarchy |
| Anonymous functions in list item props | useCallback with stable deps |
| Index as key in lists | 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 |
FlatList for large data sets | FlashList |
| Animating layout properties | Animate only transform/opacity |
TouchableOpacity | Pressable |
measure() for view dimensions | onLayout |
Falsy && for conditional rendering | Ternary or explicit comparison |
Image from react-native | expo-image |