Skip to content

Instantly share code, notes, and snippets.

@cmizony
Created January 7, 2026 22:20
Show Gist options
  • Select an option

  • Save cmizony/46d0df0de93d70489d9fb6f3f44b408b to your computer and use it in GitHub Desktop.

Select an option

Save cmizony/46d0df0de93d70489d9fb6f3f44b408b to your computer and use it in GitHub Desktop.

Frontend React Best Practices

Component Architecture

Component Organization

  • Folder Structure: Group related components in feature-based folders (components/ui/, components/features/, etc.)
  • File Naming: Use PascalCase for component files (AppBar.tsx, UserProfile.tsx)
  • Component Naming: Match component name to file name, one per file maximum
  • Separation of Concerns: Keep UI components, business logic, and state management separate

Component Design

  • Single Responsibility: Each component should have one clear purpose
  • Composition over Inheritance: Prefer composition using children props
  • Props Type: Always define explicit TypeScript types for component props
  • Type vs Interface: Use interface for data structures and object shapes, type for unions, intersections, and utility types
  • Optional Props: Use optional attributes when appropriate: interface Props { required: string; optional?: string }
  • Default Props: Use default parameters for props: const Component: React.FC<Props> = ({ prop = defaultValue }) => {}
  • Pure Components: Keep components as pure JSX - extract logic into custom hooks
  • Hook Pairing: Pair components with hooks using the same name/folder structure (e.g., ComponentName.tsx + useComponentName.ts)

State Management

Local State

  • ✅ Use useState for component-specific state
  • ✅ Initialize state with proper types: const [state, setState] = useState<Type>(initialValue)
  • ✅ Keep state as close to where it's used as possible

Global State

  • ✅ Use Context API for app-wide state (auth, theme, preferences, etc.)
  • ✅ Use state management libraries (Zustand, Redux, Jotai) for complex state
  • ✅ Avoid prop drilling beyond 2-3 levels

State Updates

  • ✅ Always use functional updates for state that depends on previous state: setCount(prev => prev + 1)
  • ✅ Batch state updates when possible
  • ✅ Avoid mutating state directly

Hooks

Custom Hooks

  • ✅ Extract reusable logic into custom hooks (useAuth, useTheme, useDataFetch, etc.)
  • ✅ Return objects for multiple values, arrays for ordered pairs
  • ✅ Keep hooks focused on a single concern
  • ✅ Co-locate hooks with components when component-specific, or in hooks/ folder when reusable

Hook Rules

  • ✅ Only call hooks at the top level (not in loops, conditions, or nested functions)
  • ✅ Only call hooks from React components or custom hooks

useEffect Best Practices

  • Avoid unless necessary: Only use useEffect for true side effects (subscriptions, timers, DOM manipulation, API calls)
  • Prefer alternatives: Use event handlers, callbacks, or derived state instead of useEffect when possible
  • ✅ Clean up subscriptions, timers, and event listeners
  • ✅ Use dependency arrays correctly
  • ✅ Avoid infinite loops by ensuring dependencies are stable
  • ✅ Prefer useCallback and useMemo for stable references

TypeScript

Type Safety

  • ✅ Always define types for props, state, and function parameters
  • Prefer interface for data structures (User, Product, etc.)
  • Use type for unions, intersections, and utility types
  • ✅ Use optional attributes when appropriate: interface Props { optional?: string }
  • ✅ Avoid any - use unknown if type is truly unknown
  • ✅ Use type guards for runtime type checking

Type Definitions

// Good - interface for data structures
interface User {
  id: string;
  name: string;
  email: string;
  role?: string; // Optional is acceptable
}

// Good - type for unions and utilities
type Status = 'idle' | 'loading' | 'success' | 'error';
type UserWithRole = User & { role: string };

// Good - interface for component props
interface UserCardProps {
  user: User;
  onEdit?: (user: User) => void;
  onDelete?: (id: string) => void;
}

// Avoid
const Component = (props: any) => {}

Performance

Optimization

  • ✅ Use React.memo for expensive components that re-render frequently
  • ✅ Use useMemo for expensive calculations
  • ✅ Use useCallback for functions passed as props to memoized components
  • ✅ Lazy load routes and heavy components: const Component = lazy(() => import('./Component'))

Rendering

  • ✅ Avoid creating objects/functions in render: move to useMemo/useCallback
  • ✅ Keep component tree shallow when possible
  • ✅ Use keys correctly in lists: stable, unique identifiers (prefer IDs over indices)

Code Quality

Naming Conventions

  • Components: PascalCase (AppBar, UserProfile)
  • Hooks: camelCase with use prefix (useAuth, useDataFetch)
  • Functions/Variables: camelCase (handleClick, isLoading)
  • Constants: UPPER_SNAKE_CASE or camelCase for module-level constants
  • Files: Match component name or use kebab-case for utilities
  • Services: camelCase (userService, apiService)

Code Organization

  • ✅ Import order: React → Third-party → Internal → Types → Styles
  • ✅ Group related imports together
  • ✅ Use absolute imports with path aliases when configured (@/components/..., ~/components/...)
  • ✅ Keep components under 200 lines; split if larger
  • ✅ Extract large forms into sub-components and hooks

Error Handling

  • ✅ Use Error Boundaries for component tree errors
  • ✅ Handle async errors with try/catch
  • ✅ Provide user-friendly error messages
  • ✅ Log errors appropriately (console.error in dev, error tracking in prod)
  • ✅ Display error states in UI with clear messaging

Accessibility

ARIA & Semantics

  • ✅ Use semantic HTML elements (<nav>, <header>, <main>, <button>)
  • ✅ Add ARIA labels for icon-only buttons: aria-label="Toggle menu"
  • ✅ Use aria-current="page" for active navigation items
  • ✅ Ensure keyboard navigation works for all interactive elements

Focus Management

  • ✅ Maintain logical tab order
  • ✅ Provide visible focus indicators
  • ✅ Manage focus for modals and dynamic content

Styling

CSS Approach

  • ✅ Use utility-first CSS (Tailwind, etc.) or CSS modules
  • ✅ Extract repeated patterns into reusable classes/components
  • ✅ Use CSS variables for theme values
  • ✅ Keep component-specific styles co-located
  • ✅ Support dark mode when applicable

Responsive Design

  • ✅ Mobile-first approach
  • ✅ Use semantic breakpoints (sm, md, lg, xl)
  • ✅ Test on multiple screen sizes
  • ✅ Use responsive utilities consistently

File Structure

src/
  components/
    ui/              # Reusable UI components (pure JSX)
    features/        # Feature-specific components
    layout/          # Layout components
  hooks/             # Custom hooks
  contexts/          # React contexts
  services/          # API and external services
  lib/               # Utility functions
  types/             # TypeScript type definitions
  pages/             # Page-level components (if using routing)
  stores/            # State management (if using Zustand/Redux)

Component + Hook Pairing Pattern

  • Keep components as pure JSX (presentation only)
  • Extract all logic, state, and side effects into a paired custom hook
  • Use matching names: ComponentName.tsx + useComponentName.ts
  • Co-locate in the same folder when component-specific, or in hooks/ when reusable

Common Patterns

Conditional Rendering

// Good
{isLoading && <Spinner />}
{error && <ErrorMessage error={error} />}

// Avoid
{isLoading ? <Spinner /> : null}

Event Handlers

// Good - inline for simple handlers
<button onClick={() => handleClick(id)}>Click</button>

// Good - extracted for complex logic
const handleSubmit = useCallback(async (e: React.FormEvent) => {
  e.preventDefault();
  // complex logic
}, [dependencies]);

Component Composition

// Good - using children
<Layout>
  <MainContent />
</Layout>

// Good - using composition with sub-components
<Form>
  <FormSection />
  <FormActions />
</Form>

Component + Hook Pattern

// Component (pure JSX)
// components/ui/UserCard.tsx
interface UserCardProps {
  user: User;
  onEdit?: (user: User) => void;
}

const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
  const { formattedDate, handleEdit } = useUserCard({ user, onEdit }); // Logic extracted to hook
  
  return (
    <div>
      {/* Pure JSX presentation */}
    </div>
  );
};

// Hook (logic)
// hooks/useUserCard.ts
export const useUserCard = ({ user, onEdit }: UseUserCardProps) => {
  const formattedDate = useMemo(() => formatDate(user.createdAt), [user.createdAt]);
  
  const handleEdit = useCallback(() => {
    onEdit?.(user);
  }, [user, onEdit]);
  
  return { formattedDate, handleEdit };
};

Form Handling

// Good - extract form logic to hook
const {
  formData,
  errors,
  isSubmitting,
  setField,
  handleSubmit,
} = useForm({ initialData, onSubmit });

// Good - use sub-components for form sections
<FormSection
  formData={formData}
  errors={errors}
  onFieldChange={setField}
/>

Service Layer Pattern

// services/userService.ts
export const createUser = async (data: UserData): Promise<User> => {
  // API operations
};

export const getUsers = async (): Promise<User[]> => {
  // Query logic
};

Data Fetching

// Good - custom hook for data fetching
const { data, loading, error } = useDataFetch<User[]>(() => getUsers());

// Good - handle loading and error states
{loading && <Spinner />}
{error && <ErrorMessage error={error} />}
{data && <DataList data={data} />}

Routing (if applicable)

Route Organization

  • ✅ Use a routing library (React Router, Next.js Router, etc.)
  • ✅ Protect routes with authentication/authorization
  • ✅ Use route-based code splitting
  • ✅ Keep route components in pages/ or routes/ folder

Testing (if applicable)

Testing Best Practices

  • ✅ Write unit tests for utilities and hooks
  • ✅ Write integration tests for components
  • ✅ Test user interactions, not implementation details
  • ✅ Use testing library best practices

Anti-Patterns to Avoid

  • ❌ Mutating state directly
  • ❌ Using array indices as keys (use stable IDs)
  • ❌ Creating components inside render
  • ❌ Ignoring TypeScript errors
  • ❌ Over-optimizing prematurely
  • ❌ Mixing business logic with UI components
  • ❌ Using inline styles when utility classes exist
  • ❌ Not handling loading and error states
  • ❌ Not cleaning up subscriptions and resources
  • ❌ Hardcoding sensitive data or API keys
  • ❌ Forgetting to handle async errors in try/catch blocks
  • ❌ Creating unnecessary re-renders with unstable references
  • ❌ Using useEffect for derived state instead of useMemo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment