スキル一覧に戻る
benaor

react-conventions

by benaor

my Claude configuration files

3🍴 0📅 2026年1月13日
GitHubで見るManusで実行

SKILL.md


name: react-conventions description: React and React Native coding conventions with TypeScript. Use this skill when creating components, writing hooks, or reviewing React/React Native code. Enforces naming conventions, component structure, typing patterns, accessibility, and performance best practices.

React Conventions

Coding conventions for React and React Native with TypeScript. It defines how to write good components, props, hooks and styles with tailwind (uniwind). By keeping in mind accessibility, performances (with and without react-compiler) and testability.

Exports & Naming

Named exports only

// ✅ Good
export const Button: FC<ButtonProps> = ({ label }) => { ... };

// ❌ Bad
export default function Button({ label }) { ... }

Component typing

Always use FC<Props> with arrow functions:

// ✅ Good
interface ButtonProps {
  label: string;
  onPress: VoidFunction;
}

export const Button: FC<ButtonProps> = ({ label, onPress }) => {
  return <Pressable onPress={onPress}><Text>{label}</Text></Pressable>;
};

// ❌ Bad
export function Button(props: ButtonProps) { ... }
export const Button = ({ label }: ButtonProps) => { ... }; // Missing FC

Naming conventions

ElementConventionExample
ComponentPascalCaseUserProfile.tsx
Props interface[Component]PropsUserProfileProps
HookcamelCase with use prefixuseAuth.ts
Hook return type[Hook]ReturnUseAuthReturn

Component Structure

Order within a component file:

// 1. Imports
import { FC, useState } from "react";
import { View, Text, Pressable } from "react-native";

// 2. Types
type UserCardProps = Readonly<{
  name: string;
  avatarUrl?: string;
  onPress: VoidFunction;
}>;

// 3. Component
export const UserCard: FC<UserCardProps> = ({ name, avatarUrl, onPress }) => {
  // 3a. Hooks
  const [isPressed, setIsPressed] = useState(false);

  // 3b. Derived state / computed values
  const displayName = name.toUpperCase();

  // 3c. Handlers
  const handlePress = () => {
    setIsPressed(true);
    onPress();
  };

  // 3d. Render
  return (
    <Pressable onPress={handlePress} className="p-4 bg-white rounded-lg">
      <Text className="text-lg font-bold">{displayName}</Text>
    </Pressable>
  );
};

JSX readability

Extract complex logic from JSX:

// ✅ Good
const showError = hasError && !isLoading;
const statusText = isOnline ? "Connected" : "Offline";

return (
  <View>
    {showError && <ErrorMessage />}
    <Text>{statusText}</Text>
  </View>
);

// ❌ Bad
return (
  <View>
    {hasError && !isLoading && <ErrorMessage />}
    <Text>{isOnline ? "Connected" : "Offline"}</Text>
  </View>
);

Props

Readonly by default

Use the Readonly utility type:

// ✅ Good
type CardProps = Readonly<{
  title: string;
  subtitle?: string;
}>;

// ❌ Bad — verbose
interface CardProps {
  readonly title: string;
  readonly subtitle?: string;
}

Children with PropsWithChildren

// ✅ Good — use PropsWithChildren
type ContainerProps = PropsWithChildren<
  Readonly<{
    padding?: number;
  }>
>;

// Or with no additional props
type WrapperProps = PropsWithChildren;

// ❌ Bad — manual children typing
type ContainerProps = Readonly<{
  children: ReactNode;
  padding?: number;
}>;

Default values via destructuring

// ✅ Good
export const Button: FC<ButtonProps> = ({
  variant = "primary",
  size = "medium",
  disabled = false,
}) => { ... };

// ❌ Bad — deprecated pattern
Button.defaultProps = {
  variant: "primary",
};

Optional vs required

Be intentional:

type FormFieldProps = Readonly<{
  // Required — component cannot function without these
  label: string;
  value: string;
  onChange: (value: string) => void;

  // Optional — sensible defaults exist
  placeholder?: string;
  disabled?: boolean;
  errorMessage?: string;
}>;

Hooks

Naming

// ✅ Good
const useAuth = () => { ... };
const useUserProfile = (userId: string) => { ... };

// ❌ Bad
const authHook = () => { ... };
const getUserProfile = () => { ... };

One hook per file

hooks/
├── useAuth.ts
├── useToggle.ts
└── useDebounce.ts

Explicit return type

// ✅ Good
interface UseToggleReturn {
  isOn: boolean;
  toggle: VoidFunction;
  setOn: VoidFunction;
  setOff: VoidFunction;
}

export const useToggle = (initial = false): UseToggleReturn => {
  const [isOn, setIsOn] = useState(initial);

  return {
    isOn,
    toggle: () => setIsOn((v) => !v),
    setOn: () => setIsOn(true),
    setOff: () => setIsOn(false),
  };
};

Rules of hooks

// ✅ Good — hooks at top level, stable order
export const Component: FC<Props> = ({ userId }) => {
  const [state, setState] = useState(null);
  const user = useUser(userId);

  // ...
};

// ❌ Bad — conditional hook
export const Component: FC<Props> = ({ userId }) => {
  if (userId) {
    const user = useUser(userId); // ❌ Conditional
  }
};

// ❌ Bad — hook in loop
items.map((item) => {
  const data = useData(item.id); // ❌ In loop
});

Performance

React Compiler

React 19+ with React Compiler handles most memoization automatically. Avoid premature optimization:

// ✅ With React Compiler — usually no manual memoization needed
export const List: FC<ListProps> = ({ items, onItemPress }) => {
  const sortedItems = items.sort((a, b) => a.name.localeCompare(b.name));

  return (
    <FlatList
      data={sortedItems}
      renderItem={({ item }) => (
        <ListItem item={item} onPress={() => onItemPress(item.id)} />
      )}
    />
  );
};

When to manually optimize

Only optimize when you measure a performance problem:

// Manual memo — only if React Compiler is not enabled or insufficient
export const ExpensiveComponent: FC<Props> = memo(({ data }) => {
  // Heavy render
});

// Manual useCallback — only for non-compiled code or specific perf issues
const handlePress = useCallback(() => {
  onPress(id);
}, [onPress, id]);

// Manual useMemo — only for genuinely expensive computations
const processedData = useMemo(() => {
  return heavyComputation(rawData);
}, [rawData]);

FlatList optimization

// ✅ Good
<FlatList
  data={items}
  keyExtractor={(item) => item.id}
  renderItem={renderItem}
  getItemLayout={(_, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
  removeClippedSubviews={true}
  maxToRenderPerBatch={10}
  windowSize={5}
/>;

// Extract renderItem to avoid recreation
const renderItem = useCallback(
  ({ item }: { item: Item }) => <ItemCard item={item} />,
  []
);

Avoid inline arrays in props

// ✅ Good — stable reference
const emptyArray: Item[] = [];

<FlatList data={items ?? emptyArray} />

// ❌ Bad — new array reference every render
<FlatList data={items ?? []} />

Styles (React Native + Tailwind)

Using Tailwind CSS via Uniwind or NativeWind.

Inline className

// ✅ Good
<View className="flex-1 p-4 bg-white">
  <Text className="text-lg font-bold text-gray-900">Title</Text>
</View>;

// ❌ Bad — StyleSheet.create
const styles = StyleSheet.create({
  container: { flex: 1, padding: 16 },
});

Conditional styles

// ✅ Good — template literals or clsx
<View className={`p-4 ${isActive ? "bg-blue-500" : "bg-gray-200"}`} />

// With clsx
<View className={clsx(
  "p-4 rounded-lg",
  isActive && "bg-blue-500",
  isDisabled && "opacity-50",
)} />

// ❌ Bad — ternary soup
<View className={isActive ? (isLarge ? "p-8 bg-blue-500" : "p-4 bg-blue-500") : "p-4 bg-gray-200"} />

Extract complex styles

// ✅ Good — extract to variable when complex
const containerClass = clsx(
  "flex-1 p-4",
  isActive && "bg-blue-500",
  isDisabled && "opacity-50 pointer-events-none",
  size === "large" && "p-8"
);

return <View className={containerClass}>...</View>;

Custom classes (Uniwind)

Uniwind includes a CSS parser. Define reusable classes in global.css:

/* global.css */
@import "tailwindcss";
@import "uniwind";

.btn {
  padding-left: 16px;
  padding-right: 16px;
  padding-top: 8px;
  padding-bottom: 8px;
  border-radius: 8px;
  font-weight: 500;
}

.btn-primary {
  background-color: #3b82f6;
  color: white;
}

.btn-secondary {
  background-color: #e5e7eb;
  color: #111827;
}

.card {
  padding: 16px;
  background-color: white;
  border-radius: 12px;
}
// Usage — combine custom classes with Tailwind utilities
<Pressable className="btn btn-primary" />
<Pressable className={clsx("btn", isPrimary ? "btn-primary" : "btn-secondary")} />
<View className="card shadow-sm" />

Theme values

Use Tailwind config for design tokens:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: "#FF6B35",
        secondary: "#004E89",
      },
      spacing: {
        18: "4.5rem",
      },
    },
  },
};

// Usage
<View className="bg-primary p-18" />;

Accessibility

Required attributes

// ✅ Good
<Pressable
  onPress={handlePress}
  accessibilityRole="button"
  accessibilityLabel="Submit form"
  accessibilityHint="Submits your information and proceeds to the next step"
  accessibilityState={{ disabled: isLoading }}
>
  <Text>Submit</Text>
</Pressable>

// ❌ Bad — no accessibility info
<Pressable onPress={handlePress}>
  <Text>Submit</Text>
</Pressable>

Accessibility roles

Use appropriate roles:

ElementRole
Clickable actionbutton
Navigation linklink
Text inputnone (handled by TextInput)
Imageimage
Header textheader
Checkboxcheckbox
Switchswitch
Tabtab

Accessibility state

<Pressable
  accessibilityRole="checkbox"
  accessibilityState={{
    checked: isChecked,
    disabled: isDisabled,
  }}
>
  <Text>Accept terms</Text>
</Pressable>

Screen reader considerations

// Group related elements
<View accessibilityRole="summary" accessibilityLabel={`${title}. ${subtitle}`}>
  <Text>{title}</Text>
  <Text>{subtitle}</Text>
</View>

// Hide decorative elements
<Image
  source={decorativeIcon}
  accessibilityElementsHidden={true}
  importantForAccessibility="no-hide-descendants"
/>

Testability

testID from constants only

Never hardcode testIDs. Always use the centralized constants:

// constants/testIDs.ts
export const TestIDs = {
  Login: {
    emailInput: "login-email-input",
    passwordInput: "login-password-input",
    submitButton: "login-submit-button",
    errorMessage: "login-error-message",
  },
  Profile: {
    avatar: "profile-avatar",
    nameText: "profile-name-text",
    editButton: "profile-edit-button",
  },
} as const;

Usage

// ✅ Good — from constants
import { TestIDs } from "@/constants/testIDs";

<Pressable testID={TestIDs.Login.submitButton} onPress={handleSubmit}>
  <Text>Submit</Text>
</Pressable>

<TextInput
  testID={TestIDs.Login.emailInput}
  value={email}
  onChangeText={setEmail}
/>

// ❌ Bad — hardcoded
<Pressable testID="submit-button" onPress={handleSubmit}>
  <Text>Submit</Text>
</Pressable>

Naming convention

Pattern: [screen]-[element]-[action?]
Format: kebab-case

Examples:
- login-email-input
- profile-save-button
- home-user-list
- settings-notifications-toggle

スコア

総合スコア

35/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

0/5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です