From skills
React/TypeScript frontend development standards for MBU ID services. Use this skill for any React/TypeScript frontend work including component structure, state management, form handling, API integration, and styling. Apply when building new components, implementing hooks, managing state with Context/Zustand/React Query, handling forms with React Hook Form + Zod, or setting up TypeScript types. This skill enforces type safety and modern React patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/skills:mbu-reactThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
React and TypeScript coding standards for MBU frontend applications.
React and TypeScript coding standards for MBU frontend applications.
any, explicit types everywheresrc/
├── components/ # Reusable UI components
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx
│ │ ├── Button.module.css
│ │ └── index.ts
│ └── Input/
│ ├── Input.tsx
│ └── index.ts
├── features/ # Feature-based modules
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── services/
│ │ └── types.ts
│ └── warehouse/
│ ├── components/
│ ├── hooks/
│ └── types.ts
├── hooks/ # Shared custom hooks
│ ├── useAuth.ts
│ └── useFetch.ts
├── services/ # API clients
│ ├── api.ts
│ └── warehouse.ts
├── types/ # Shared TypeScript types
│ ├── api.ts
│ └── models.ts
├── utils/ # Utility functions
│ ├── format.ts
│ └── validation.ts
├── App.tsx
└── main.tsx
Button.tsx, UserProfile.tsxuseAuth.ts, formatDate.tsbutton.module.css// ✅ Correct — PascalCase, named export
export const Button = () => { ... }
// ❌ Wrong — default export
export default function Button() { ... }
// ❌ Wrong — camelCase
export const button = () => { ... }
// ✅ Correct — camelCase, starts with "use"
export const useAuth = () => { ... }
export const useFetchWarehouse = () => { ... }
// ❌ Wrong — missing "use" prefix
export const auth = () => { ... }
// ✅ Correct — PascalCase, descriptive
interface UserProfile {
id: string;
name: string;
}
type ButtonVariant = 'primary' | 'secondary';
// ❌ Wrong — prefixes
interface IUserProfile { ... } // No "I" prefix
type TButtonVariant = ... // No "T" prefix
// ✅ Correct — explicit interface
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
onClick?: () => void;
children: React.ReactNode;
}
export const Button = ({
variant = 'primary',
size = 'md',
disabled = false,
onClick,
children
}: ButtonProps) => {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
};
// ❌ Wrong — inline props, no types
export const Button = ({ variant, size, children }) => { ... }
// ✅ Correct — explicit event types
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
// ...
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
// ❌ Wrong — any type
const handleClick = (event: any) => { ... }
// ✅ Correct — explicit state types
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [items, setItems] = useState<Item[]>([]);
// ❌ Wrong — implicit any
const [user, setUser] = useState(null);
// ✅ Correct — typed responses
interface ApiResponse<T> {
data: T;
message: string;
status: number;
}
interface User {
id: string;
name: string;
email: string;
}
const fetchUser = async (id: string): Promise<ApiResponse<User>> => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};
// ❌ Wrong — untyped
const fetchUser = async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};
// ✅ Correct — arrow function, explicit return type
export const UserCard = ({ user }: { user: User }): JSX.Element => {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
};
// Also acceptable — implicit return type
export const UserCard = ({ user }: { user: User }) => {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
};
interface CardProps {
title: string;
children: React.ReactNode;
}
export const Card = ({ title, children }: CardProps) => {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-content">{children}</div>
</div>
);
};
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}
export const Button = ({
variant = 'primary',
size = 'md',
children
}: ButtonProps) => {
return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
};
// ✅ Correct — explicit conditions
export const UserStatus = ({ user }: { user: User | null }) => {
if (!user) {
return <div>Loading...</div>;
}
if (user.isActive) {
return <div className="status-active">Active</div>;
}
return <div className="status-inactive">Inactive</div>;
};
// Also acceptable — ternary for simple cases
export const UserStatus = ({ isActive }: { isActive: boolean }) => {
return (
<div className={isActive ? 'status-active' : 'status-inactive'}>
{isActive ? 'Active' : 'Inactive'}
</div>
);
};
// ✅ Correct — typed hook with clear return
interface UseAuthReturn {
user: User | null;
loading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
export const useAuth = (): UseAuthReturn => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const login = async (email: string, password: string) => {
setLoading(true);
try {
const response = await api.login(email, password);
setUser(response.data);
} finally {
setLoading(false);
}
};
const logout = () => {
setUser(null);
};
return { user, loading, login, logout };
};
// ✅ Correct — explicit dependencies
useEffect(() => {
fetchUser(userId);
}, [userId]);
// ❌ Wrong — missing dependencies
useEffect(() => {
fetchUser(userId);
}, []);
// ❌ Wrong — empty deps when not needed
useEffect(() => {
console.log('Component mounted');
}, []); // Should be removed if only logging
// ✅ Correct — memoize expensive computations
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
// ✅ Correct — memoize callbacks passed to children
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
// ❌ Wrong — unnecessary memoization
const name = useMemo(() => user.name, [user]); // Too simple
// ✅ Correct — for component-local state
export const Counter = () => {
const [count, setCount] = useState<number>(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
// ✅ Correct — for shared state across components
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
const login = async (email: string, password: string) => {
const response = await api.login(email, password);
setUser(response.data);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};
import { create } from 'zustand';
interface WarehouseState {
warehouses: Warehouse[];
loading: boolean;
fetchWarehouses: () => Promise<void>;
addWarehouse: (warehouse: Warehouse) => void;
}
export const useWarehouseStore = create<WarehouseState>((set) => ({
warehouses: [],
loading: false,
fetchWarehouses: async () => {
set({ loading: true });
const response = await api.getWarehouses();
set({ warehouses: response.data, loading: false });
},
addWarehouse: (warehouse) =>
set((state) => ({ warehouses: [...state.warehouses, warehouse] })),
}));
// services/api.ts
interface ApiError {
message: string;
status: number;
}
class ApiClient {
private baseURL: string;
constructor(baseURL: string) {
this.baseURL = baseURL;
}
async request<T>(
endpoint: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(`${this.baseURL}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
});
if (!response.ok) {
const error: ApiError = {
message: await response.text(),
status: response.status,
};
throw error;
}
return response.json();
}
get<T>(endpoint: string): Promise<T> {
return this.request<T>(endpoint);
}
post<T>(endpoint: string, data: unknown): Promise<T> {
return this.request<T>(endpoint, {
method: 'POST',
body: JSON.stringify(data),
});
}
}
export const api = new ApiClient(import.meta.env.VITE_API_URL);
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Fetch hook
export const useWarehouses = () => {
return useQuery({
queryKey: ['warehouses'],
queryFn: () => api.get<Warehouse[]>('/warehouses'),
});
};
// Mutation hook
export const useCreateWarehouse = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateWarehouseInput) =>
api.post<Warehouse>('/warehouses', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['warehouses'] });
},
});
};
// Usage in component
export const WarehouseList = () => {
const { data: warehouses, isLoading, error } = useWarehouses();
const createWarehouse = useCreateWarehouse();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{warehouses?.map((warehouse) => (
<div key={warehouse.id}>{warehouse.name}</div>
))}
</div>
);
};
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// Validation schema
const warehouseSchema = z.object({
name: z.string().min(3, 'Name must be at least 3 characters'),
address: z.string().min(5, 'Address is required'),
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number'),
});
type WarehouseFormData = z.infer<typeof warehouseSchema>;
export const WarehouseForm = () => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<WarehouseFormData>({
resolver: zodResolver(warehouseSchema),
});
const onSubmit = async (data: WarehouseFormData) => {
await api.post('/warehouses', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Name</label>
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}
</div>
<div>
<label>Address</label>
<input {...register('address')} />
{errors.address && <span>{errors.address.message}</span>}
</div>
<div>
<label>Phone</label>
<input {...register('phone')} />
{errors.phone && <span>{errors.phone.message}</span>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
};
// Button.module.css
.button {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
border: none;
cursor: pointer;
}
.primary {
background-color: #007bff;
color: white;
}
.secondary {
background-color: #6c757d;
color: white;
}
// Button.tsx
import styles from './Button.module.css';
interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
}
export const Button = ({ variant = 'primary', children }: ButtonProps) => {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);
};
interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
}
export const Button = ({ variant = 'primary', children }: ButtonProps) => {
const baseClasses = 'px-4 py-2 rounded font-medium';
const variantClasses = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-600 text-white hover:bg-gray-700',
};
return (
<button className={`${baseClasses} ${variantClasses[variant]}`}>
{children}
</button>
);
};
import { lazy, Suspense } from 'react';
// Lazy load components
const WarehouseList = lazy(() => import('./features/warehouse/WarehouseList'));
const UserProfile = lazy(() => import('./features/user/UserProfile'));
export const App = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/warehouses" element={<WarehouseList />} />
<Route path="/profile" element={<UserProfile />} />
</Routes>
</Suspense>
);
};
import { memo } from 'react';
// Memoize expensive components
export const ExpensiveComponent = memo(({ data }: { data: Data }) => {
// Expensive rendering logic
return <div>{/* ... */}</div>;
});
// Custom comparison function
export const UserCard = memo(
({ user }: { user: User }) => {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Button } from './Button';
describe('Button', () => {
it('renders children correctly', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('calls onClick when clicked', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('applies correct variant class', () => {
render(<Button variant="secondary">Click me</Button>);
const button = screen.getByText('Click me');
expect(button).toHaveClass('btn-secondary');
});
});
any Type// ❌ Wrong
const handleData = (data: any) => { ... }
// ✅ Correct
const handleData = (data: User) => { ... }
// Or for unknown data
const handleData = (data: unknown) => { ... }
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
// ✅ Correct
export const UserCard = ({ name, email }: UserCardProps) => { ... }
// ❌ Wrong
export const UserCard = (props: UserCardProps) => {
return <div>{props.name}</div>;
}
// ✅ Correct
export const Button = () => { ... }
// ❌ Wrong
export default Button;
// ✅ Correct — small, focused components
export const UserCard = ({ user }: { user: User }) => {
return (
<div className="user-card">
<UserAvatar user={user} />
<UserInfo user={user} />
<UserActions user={user} />
</div>
);
};
// ❌ Wrong — too much in one component
export const UserCard = ({ user }: { user: User }) => {
return (
<div className="user-card">
{/* 100+ lines of JSX */}
</div>
);
};
// ✅ Correct
const API_URL = import.meta.env.VITE_API_URL;
// ❌ Wrong — hardcoded
const API_URL = 'https://api.example.com';
// ✅ Correct
export const UserList = () => {
const { data, isLoading, error } = useUsers();
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return <EmptyState />;
return <div>{/* render data */}</div>;
};
any types usedGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub mbu-id/claude-plugins --plugin skills