Design Patterns

Frontend Design Patterns

Component Patterns

Compound Components

// Menu component example
const Menu = ({ children }: { children: React.ReactNode }) => {
  const [openItem, setOpenItem] = useState<string | null>(null)

  return (
    <MenuContext.Provider value={{ openItem, setOpenItem }}>
      <nav>{children}</nav>
    </MenuContext.Provider>
  )
}

Menu.Item = ({ id, children }: { id: string; children: React.ReactNode }) => {
  const { openItem, setOpenItem } = useMenuContext()
  return (
    <div onClick={() => setOpenItem(id)}>
      {children}
    </div>
  )
}

Render Props

interface ListProps<T> {
  items: T[]
  render: (item: T) => React.ReactNode
}

function List<T>({ items, render }: ListProps<T>) {
  return (
    <div>
      {items.map((item, index) => (
        <div key={index}>{render(item)}</div>
      ))}
    </div>
  )
}

Hook Patterns

Custom Hooks

function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value)

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay)
    return () => clearTimeout(timer)
  }, [value, delay])

  return debouncedValue
}

Hook Composition

function useProjectData(projectId: string) {
  const { data: project } = useProject(projectId)
  const { data: metrics } = useMetrics(projectId)
  const { data: activities } = useActivities(projectId)

  return {
    project,
    metrics,
    activities
  }
}

State Management Patterns

Container/Presenter Pattern

// Container
const ProjectListContainer = () => {
  const { data, isLoading } = useProjects()
  return <ProjectList projects={data} isLoading={isLoading} />
}

// Presenter
const ProjectList = ({ projects, isLoading }: ProjectListProps) => {
  if (isLoading) return <LoadingSpinner />
  return (
    <div>
      {projects.map(project => (
        <ProjectCard key={project.id} project={project} />
      ))}
    </div>
  )
}

State Reducer Pattern

type State = { count: number }
type Action = { type: 'increment' | 'decrement'; payload?: number }

function reducer(state: State, action: Action): State {
  switch (action.action) {
    case 'increment':
      return { count: state.count + (action.payload ?? 1) }
    case 'decrement':
      return { count: state.count - (action.payload ?? 1) }
    default:
      return state
  }
}

Form Patterns

Form Builder Pattern

const FormBuilder = () => {
  const { register, handleSubmit } = useForm()

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Input
        {...register('email', {
          required: 'Email is required',
          pattern: {
            value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
            message: 'Invalid email address'
          }
        })}
      />
    </form>
  )
}

Form Controller Pattern

const FormController = ({ control }: { control: Control<FormData> }) => {
  return (
    <Controller
      name="select"
      control={control}
      rules={{ required: true }}
      render={({ field }) => <Select {...field} />}
    />
  )
}

Error Handling Patterns

Error Boundary Pattern

class ErrorBoundary extends React.Component<Props, State> {
  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error }
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    logErrorToService(error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return <ErrorFallback error={this.state.error} />
    }
    return this.props.children
  }
}

Try-Catch Pattern

const AsyncComponent = () => {
  const [error, setError] = useState<Error | null>(null)

  const handleAsyncOperation = async () => {
    try {
      await someAsyncOperation()
    } catch (err) {
      setError(err as Error)
    }
  }

  if (error) return <ErrorMessage error={error} />
  return <div>Content</div>
}

Performance Patterns

Memoization Pattern

const MemoizedComponent = React.memo(({ data }: Props) => {
  return <ExpensiveRendering data={data} />
}, (prevProps, nextProps) => {
  return prevProps.data.id === nextProps.data.id
})

Virtual List Pattern

const VirtualList = ({ items }: { items: Item[] }) => {
  const [visibleItems, setVisibleItems] = useState<Item[]>([])

  useEffect(() => {
    const observer = new IntersectionObserver(
      entries => {
        // Update visible items based on intersection
      },
      { threshold: 0.5 }
    )
    return () => observer.disconnect()
  }, [])

  return (
    <div className="virtual-list">
      {visibleItems.map(item => (
        <ListItem key={item.id} item={item} />
      ))}
    </div>
  )
}