← Back to list

screen-generator
by mesbahtanvir
ishkul.org
⭐ 1🍴 0📅 Jan 18, 2026
SKILL.md
name: screen-generator description: Generates new React Native screens following Ishkul patterns. Creates screen component with proper hooks ordering, state management, loading/error/success states, navigation setup, and matching test file. Use when adding new screens to the app.
Screen Generator
Creates new React Native screens following Ishkul's established patterns.
What Gets Created
When generating a new screen, create:
- Screen component:
frontend/src/screens/ScreenName.tsx - Test file:
frontend/src/screens/__tests__/ScreenName.test.tsx - Navigation update: Add to
frontend/src/types/navigation.ts - Navigator update: Add to
frontend/src/navigation/AppNavigator.tsx
Screen Template
import React, { useCallback, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
ActivityIndicator,
RefreshControl,
} from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
// State management
import { useScreenNameStore } from '../state/screenNameStore';
// Custom hooks
import { useTheme } from '../hooks/useTheme';
// Components
import { Button } from '../components/Button';
import { ErrorBanner } from '../components/ErrorBanner';
import { LearningLayout } from '../components/LearningLayout';
// Types
import { RootStackParamList } from '../types/navigation';
// Theme
import { Spacing } from '../theme/spacing';
import { Typography } from '../theme/typography';
type ScreenNameProps = NativeStackScreenProps<RootStackParamList, 'ScreenName'>;
export const ScreenName: React.FC<ScreenNameProps> = ({ navigation, route }) => {
// ==========================================================================
// HOOKS SECTION - All hooks MUST be called at the top, before any conditionals
// This prevents React Rules of Hooks violations (React error #310)
// ==========================================================================
// 1. Zustand store hooks first
const {
data,
isLoading,
error,
fetchData,
clearError,
} = useScreenNameStore();
// 2. Theme and navigation hooks
const { colors } = useTheme();
// 3. Route params extraction
const { id } = route.params;
// 4. Refs (if needed)
// const scrollViewRef = useRef<ScrollView>(null);
// 5. Extract state used in multiple render paths BEFORE conditionals
const hasData = data !== null;
const dataItems = data?.items ?? [];
// 6. Callbacks with useCallback BEFORE conditionals
const handleRefresh = useCallback(() => {
fetchData(id);
}, [fetchData, id]);
const handleRetry = useCallback(() => {
clearError();
fetchData(id);
}, [clearError, fetchData, id]);
const handleItemPress = useCallback((itemId: string) => {
navigation.navigate('ItemDetail', { itemId });
}, [navigation]);
// 7. Effects after hooks
useEffect(() => {
fetchData(id);
}, [fetchData, id]);
// ==========================================================================
// CONDITIONAL RETURNS - Safe after all hooks are called
// ==========================================================================
// Loading state
if (isLoading && !hasData) {
return (
<LearningLayout
title="Loading..."
onBackPress={() => navigation.goBack()}
showProgress={false}
>
<View style={styles.centerContainer} testID="loading-indicator">
<ActivityIndicator size="large" color={colors.primary} />
<Text style={[styles.loadingText, { color: colors.textSecondary }]}>
Loading content...
</Text>
</View>
</LearningLayout>
);
}
// Error state
if (error && !hasData) {
return (
<LearningLayout
title="Error"
onBackPress={() => navigation.goBack()}
showProgress={false}
>
<View style={styles.centerContainer} testID="error-container">
<ErrorBanner message={error} onDismiss={clearError} />
<Button
title="Retry"
onPress={handleRetry}
variant="primary"
style={styles.retryButton}
/>
</View>
</LearningLayout>
);
}
// Empty state
if (!hasData) {
return (
<LearningLayout
title="Not Found"
onBackPress={() => navigation.goBack()}
showProgress={false}
>
<View style={styles.centerContainer} testID="empty-container">
<Text style={[styles.emptyText, { color: colors.textSecondary }]}>
No content found
</Text>
<Button
title="Go Back"
onPress={() => navigation.goBack()}
variant="outline"
style={styles.retryButton}
/>
</View>
</LearningLayout>
);
}
// ==========================================================================
// SUCCESS STATE - Main content
// ==========================================================================
return (
<LearningLayout
title={data.title}
onBackPress={() => navigation.goBack()}
showProgress={true}
progress={0.5}
>
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}
refreshControl={
<RefreshControl
refreshing={isLoading}
onRefresh={handleRefresh}
colors={[colors.primary]}
/>
}
testID="content-container"
>
{/* Error banner for non-blocking errors */}
{error && (
<ErrorBanner
message={error}
onDismiss={clearError}
style={styles.errorBanner}
/>
)}
{/* Main content */}
<View style={styles.section}>
<Text style={[styles.sectionTitle, { color: colors.text }]}>
Content Section
</Text>
{dataItems.map((item) => (
<View key={item.id} style={styles.itemContainer}>
<Text style={[styles.itemText, { color: colors.text }]}>
{item.title}
</Text>
<Button
title="View"
onPress={() => handleItemPress(item.id)}
variant="outline"
size="small"
/>
</View>
))}
</View>
</ScrollView>
</LearningLayout>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
contentContainer: {
padding: Spacing.md,
paddingBottom: Spacing.xxl,
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: Spacing.xl,
},
loadingText: {
marginTop: Spacing.md,
...Typography.body,
},
emptyText: {
...Typography.body,
textAlign: 'center',
marginBottom: Spacing.lg,
},
retryButton: {
marginTop: Spacing.lg,
},
errorBanner: {
marginBottom: Spacing.md,
},
section: {
marginBottom: Spacing.lg,
},
sectionTitle: {
...Typography.h2,
marginBottom: Spacing.md,
},
itemContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: Spacing.sm,
borderBottomWidth: 1,
borderBottomColor: '#E5E5E5',
},
itemText: {
...Typography.body,
flex: 1,
},
});
Navigation Type Update
Add to frontend/src/types/navigation.ts:
export type RootStackParamList = {
// ... existing routes
ScreenName: { id: string };
// Add any new routes the screen navigates to
ItemDetail: { itemId: string };
};
Navigator Update
Add to frontend/src/navigation/AppNavigator.tsx:
import { ScreenName } from '../screens/ScreenName';
// In the navigator:
<Stack.Screen
name="ScreenName"
component={ScreenName}
options={{
headerShown: false, // Using LearningLayout
}}
/>
Zustand Store Template
If a new store is needed, create frontend/src/state/screenNameStore.ts:
import { create } from 'zustand';
import { apiClient } from '../services/api/client';
interface DataItem {
id: string;
title: string;
}
interface ScreenData {
id: string;
title: string;
items: DataItem[];
}
interface ScreenNameState {
// State
data: ScreenData | null;
isLoading: boolean;
error: string | null;
// Actions
fetchData: (id: string) => Promise<ScreenData | null>;
clearError: () => void;
reset: () => void;
}
export const useScreenNameStore = create<ScreenNameState>((set, get) => ({
data: null,
isLoading: false,
error: null,
fetchData: async (id: string) => {
set({ isLoading: true, error: null });
try {
const response = await apiClient.get<ScreenData>(`/api/resource/${id}`);
set({ data: response, isLoading: false });
return response;
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to load';
set({ error: message, isLoading: false });
return null;
}
},
clearError: () => set({ error: null }),
reset: () => set({ data: null, isLoading: false, error: null }),
}));
Test File Template
Create frontend/src/screens/__tests__/ScreenName.test.tsx - see test-generator skill for full template.
Key test sections:
- Loading State
- Error State
- Empty State
- Success State
- State Transitions (Critical!)
- User Interactions
Checklist Before Completing
- Screen component created with proper hooks ordering
- All hooks called BEFORE conditional returns
- Loading, error, empty, success states handled
- Navigation types updated
- Navigator updated
- Zustand store created (if needed)
- Test file created with state transition tests
- TypeScript compiles:
npm run type-check - Tests pass:
npm test -- --testPathPattern="ScreenName"
When to Use
- When adding a new feature that needs its own screen
- When creating list/detail views
- When building forms or interactive pages
- When adding settings or profile pages
Score
Total Score
60/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon