Sniply Blog

Frontend Design Patterns — A Practical Guide

This guide focuses on practical patterns you can apply today in React/Next.js (but most concepts are framework‑agnostic).


Core Principles


Component Patterns

1) Presentational vs Container (Smart/Dumb)

// Container
function UserCardContainer() {
  const { data } = useQuery({ queryKey: ['user'], queryFn: fetchUser })
  return <UserCard user={data} />
}

// Presentational
function UserCard({ user }) {
  return <div className="card">Hello, {user.name}</div>
}

2) Controlled vs Uncontrolled

// Controlled
function NameInputControlled() {
  const [name, setName] = useState('')
  return <input value={name} onChange={e => setName(e.target.value)} />
}

// Uncontrolled
function NameInputUncontrolled() {
  const ref = useRef(null)
  const submit = () => console.log(ref.current?.value)
  return <><input ref={ref} /><button onClick={submit}>Save</button></>
}

3) Compound Components

Let parent own state; children communicate via context.

const TabsContext = createContext(null)

export function Tabs({ children }) {
  const [active, setActive] = useState(0)
  return <TabsContext.Provider value={{active, setActive}}>{children}</TabsContext.Provider>
}

Tabs.List = function List({ children }) { return <div role="tablist">{children}</div> }
Tabs.Tab  = function Tab({ index, children }) {
  const ctx = useContext(TabsContext)
  return <button role="tab" aria-selected={ctx.active===index} onClick={() => ctx.setActive(index)}>{children}</button>
}
Tabs.Panel = function Panel({ index, children }) {
  const ctx = useContext(TabsContext)
  return ctx.active===index ? <div role="tabpanel">{children}</div> : null
}

4) Render Props / HOCs / Custom Hooks

// Custom hook (logic)
function useCountdown(ms: number) {
  const [left, setLeft] = useState(ms)
  useEffect(() => { const id = setInterval(() => setLeft(v => Math.max(0, v-1000)), 1000); return () => clearInterval(id) }, [])
  return left
}

// Usage
function OfferTimer() {
  const left = useCountdown(10_000)
  return <p>Time left: {Math.ceil(left/1000)}s</p>
}

State Management Patterns

// Server state example with React Query
const { data, isLoading, error } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos })

Data Fetching & Boundaries

// Error boundary (simplified)
class ErrorBoundary extends React.Component {
  state = { hasError: false }
  static getDerivedStateFromError() { return { hasError: true } }
  render() { return this.state.hasError ? <p>Something went wrong.</p> : this.props.children }
}

Styling Patterns

// MUI theme tokens
const theme = createTheme({
  palette: { primary: { main: '#0d6efd' } },
  shape: { borderRadius: 12 },
})

Layout & Composition

function Card({ header, children, footer }) {
  return (<div className="card">
    <div className="card__header">{header}</div>
    <div className="card__body">{children}</div>
    <div className="card__footer">{footer}</div>
  </div>)
}

Performance Patterns

const Chart = dynamic(() => import('./Chart'), { ssr: false })

Architecture Patterns


Testing Patterns

// RTL example
render(<Button onClick={fn} />)
await user.click(screen.getByRole('button'))
expect(fn).toHaveBeenCalled()

Accessibility & UX


Error Handling & Resilience

toast.promise(api.save(data), {
  loading: 'Saving…',
  success: 'Saved!',
  error: 'Failed to save',
})

Checklist (TL;DR)


Want a Next.js App Router + MUI starter that applies these patterns? I can add a ready‑to‑drop template.

Frontend Design Patterns — A Practical Guide · Sniply