Component Template
A standard template for creating React components.
Standard Template
import { memo, useState, useMemo, useCallback, useEffect, type FC } from 'react';
import { cn } from '@/utils/cn';
// 1. Types at top
interface ComponentNameProps {
requiredProp: string;
optionalProp?: boolean;
onAction?: () => void;
className?: string;
children?: React.ReactNode;
}
// 2. Constants
const DEFAULT_VALUE = 'default';
// 3. Component
export const ComponentName: FC<ComponentNameProps> = memo(({
requiredProp,
optionalProp = false,
onAction,
className,
children,
}) => {
// 4. Hooks first
const [state, setState] = useState(false);
// 5. Derived state
const derivedValue = useMemo(() => {
return computeValue(requiredProp);
}, [requiredProp]);
// 6. Event handlers
const handleClick = useCallback(() => {
onAction?.();
}, [onAction]);
// 7. Effects
useEffect(() => {
// Side effect
}, [dependency]);
// 8. Early returns
if (!requiredProp) return null;
// 9. Render
return (
<div className={cn('base-styles', className)}>
{children}
</div>
);
});
// 10. Display name
ComponentName.displayName = 'ComponentName';Section Breakdown
1. Types at Top
Always define props interface before the component:
interface UserCardProps {
user: User;
variant?: 'default' | 'compact';
onEdit?: (user: User) => void;
}2. Constants
Module-level constants used by the component:
const ANIMATION_DURATION = 200;
const SIZE_CLASSES = {
sm: 'p-2 text-sm',
md: 'p-4 text-base',
lg: 'p-6 text-lg',
};3. Hooks Order
Always follow this order:
useStateuseReduceruseContext- Custom hooks
useMemouseCallbackuseEffect
4. Early Returns
Handle edge cases before the main render:
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
if (!data) return null;5. Render Section
Keep JSX clean and readable:
return (
<article className={cn('card', className)}>
<header className="card-header">
<h2>{title}</h2>
</header>
<div className="card-body">
{children}
</div>
</article>
);Form Component Template
interface FormComponentProps {
initialData?: FormData;
onSubmit: (data: FormData) => Promise<void>;
onCancel?: () => void;
}
export const FormComponent: FC<FormComponentProps> = ({
initialData,
onSubmit,
onCancel,
}) => {
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: initialData ?? defaultFormValues,
});
const handleSubmit = form.handleSubmit(async (data) => {
try {
await onSubmit(data);
form.reset();
} catch (error) {
// Handle error
}
});
return (
<form onSubmit={handleSubmit}>
<FormField
control={form.control}
name="fieldName"
render={({ field }) => (
<Input {...field} placeholder="Enter value" />
)}
/>
<div className="flex gap-2 mt-4">
{onCancel && (
<Button type="button" variant="secondary" onClick={onCancel}>
Cancel
</Button>
)}
<Button type="submit" isLoading={form.formState.isSubmitting}>
Submit
</Button>
</div>
</form>
);
};List Component Template
interface ListComponentProps<T> {
items: T[];
renderItem: (item: T) => ReactNode;
keyExtractor: (item: T) => string;
emptyMessage?: string;
isLoading?: boolean;
}
export function ListComponent<T>({
items,
renderItem,
keyExtractor,
emptyMessage = 'No items found',
isLoading,
}: ListComponentProps<T>) {
if (isLoading) {
return <ListSkeleton />;
}
if (items.length === 0) {
return <EmptyState message={emptyMessage} />;
}
return (
<ul className="space-y-2">
{items.map((item) => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}