This synthesizes every phase covered in Modules 2 and 3 into one cohesive component: a live analytics dashboard that streams metrics over a WebSocket, tracks window size for responsive chart re-layout, preserves scroll position when new rows arrive via a snapshot, skips wasted re-renders with shouldComponentUpdate, and is wrapped in its own isolated Error Boundary so a rendering bug in the dashboard cannot take down the rest of the app.

src/components/AnalyticsDashboard.jsxjavascript
import { Component, createRef } from 'react'
import ErrorBoundary from './ErrorBoundary.jsx'

class AnalyticsDashboardInner extends Component {
  feedListRef = createRef()
  socket = null
  resizeObserver = null
  isUnmounted = false

  state = {
    events: [],
    viewportWidth: window.innerWidth,
    connectionStatus: 'connecting'
  }

  componentDidMount() {
    this.connectSocket()

    // ResizeObserver on the panel itself (not just window) — catches layout
    // changes from sidebar toggles, not only browser window resizing.
    this.resizeObserver = new ResizeObserver((entries) => {
      const width = entries[0]?.contentRect.width
      if (width) this.setState({ viewportWidth: width })
    })
    if (this.feedListRef.current) {
      this.resizeObserver.observe(this.feedListRef.current)
    }
  }

  connectSocket() {
    this.socket = new WebSocket('wss://api.example.com/analytics/stream')

    this.socket.addEventListener('open', () => {
      if (!this.isUnmounted) this.setState({ connectionStatus: 'connected' })
    })

    this.socket.addEventListener('message', (event) => {
      if (this.isUnmounted) return
      const incomingEvent = JSON.parse(event.data)
      this.setState((prev) => ({
        // Cap the buffer so a long-running dashboard session doesn't
        // accumulate unbounded memory from an endless event stream.
        events: [...prev.events, incomingEvent].slice(-500)
      }))
    })

    this.socket.addEventListener('close', () => {
      if (!this.isUnmounted) this.setState({ connectionStatus: 'disconnected' })
    })
  }

  // Performance guardrail: only re-render when the event count changes or
  // the viewport crosses a breakpoint that actually affects layout — not on
  // every pixel of a drag-resize.
  shouldComponentUpdate(nextProps, nextState) {
    const eventsChanged = nextState.events.length !== this.state.events.length
    const statusChanged = nextState.connectionStatus !== this.state.connectionStatus

    const prevBreakpoint = this.state.viewportWidth < 768 ? 'narrow' : 'wide'
    const nextBreakpoint = nextState.viewportWidth < 768 ? 'narrow' : 'wide'
    const breakpointChanged = prevBreakpoint !== nextBreakpoint

    return eventsChanged || statusChanged || breakpointChanged
  }

  // Pin scroll position when new events arrive, exactly like the chat
  // example in Module 2 — read the pre-commit scroll state here.
  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevState.events.length === this.state.events.length) return null

    const list = this.feedListRef.current
    if (!list) return null

    const distanceFromBottom = list.scrollHeight - list.scrollTop - list.clientHeight
    return distanceFromBottom < 80 ? 'pin-to-bottom' : null
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot === 'pin-to-bottom' && this.feedListRef.current) {
      const list = this.feedListRef.current
      list.scrollTop = list.scrollHeight
    }
  }

  componentWillUnmount() {
    this.isUnmounted = true
    this.resizeObserver?.disconnect()
    this.socket?.close()
  }

  render() {
    const { events, connectionStatus, viewportWidth } = this.state
    const isNarrow = viewportWidth < 768

    return (
      <section className={`analytics-dashboard ${isNarrow ? 'analytics-dashboard--narrow' : ''}`}>
        <header className="analytics-dashboard__header">
          <h2>Live Analytics</h2>
          <span className={`status-pill status-pill--${connectionStatus}`}>
            {connectionStatus}
          </span>
        </header>

        <ul className="analytics-dashboard__feed" ref={this.feedListRef}>
          {events.map((evt) => (
            <li key={evt.id} className="analytics-dashboard__event">
              <span className="analytics-dashboard__event-type">{evt.type}</span>
              <span className="analytics-dashboard__event-value">{evt.value}</span>
            </li>
          ))}
        </ul>
      </section>
    )
  }
}

// The dashboard is deliberately wrapped in its own ErrorBoundary at export
// time — a malformed event payload or a chart-rendering bug inside this
// subtree shows a local fallback instead of unmounting the entire app shell.
function AnalyticsDashboard(props) {
  return (
    <ErrorBoundary>
      <AnalyticsDashboardInner {...props} />
    </ErrorBoundary>
  )
}

export default AnalyticsDashboard
src/components/AnalyticsDashboard.csscss
.analytics-dashboard {
  display: flex;
  flex-direction: column;
  height: 480px;
  border: 1px solid #2a2f3a;
  border-radius: 12px;
  background: #12151c;
  color: #e6e8eb;
  overflow: hidden;
}

.analytics-dashboard__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid #2a2f3a;
}

.analytics-dashboard__header h2 {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
}

.status-pill {
  padding: 0.25rem 0.65rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.status-pill--connected {
  background: rgba(34, 197, 94, 0.15);
  color: #4ade80;
}

.status-pill--connecting {
  background: rgba(234, 179, 8, 0.15);
  color: #facc15;
}

.status-pill--disconnected {
  background: rgba(239, 68, 68, 0.15);
  color: #f87171;
}

.analytics-dashboard__feed {
  flex: 1;
  margin: 0;
  padding: 0.5rem 0;
  list-style: none;
  overflow-y: auto;
}

.analytics-dashboard__event {
  display: flex;
  justify-content: space-between;
  padding: 0.6rem 1.25rem;
  border-bottom: 1px solid #1c1f27;
  font-size: 0.875rem;
}

.analytics-dashboard__event-type {
  color: #93c5fd;
  font-weight: 500;
}

.analytics-dashboard--narrow .analytics-dashboard__event {
  flex-direction: column;
  gap: 0.15rem;
}

.error-boundary-fallback {
  padding: 1.5rem;
  border: 1px solid #f87171;
  border-radius: 12px;
  background: rgba(239, 68, 68, 0.08);
  color: #fca5a5;
}

HookOrderPhasesetState safe?Primary use case
constructor(props)1 (mount only)RenderDirect assignment only (this.state = {...}), never this.setState()Initialize state, bind handlers
static getDerivedStateFromProps(props, state)2 (mount), 1 (update)RenderN/A — returns state object or null, no imperative setStateRare: reset controlled state when a prop-driven record id changes
render()3 (mount), 3 (update)RenderNoReturn JSX — pure function of props/state
shouldComponentUpdate(nextProps, nextState)2 (update only)RenderNoSkip render()/commit for performance
getSnapshotBeforeUpdate(prevProps, prevState)4 (update only)Render (pre-commit)NoRead DOM state before mutations apply (e.g. scroll position)
componentDidMount()4 (mount only)CommitYesData fetching, subscriptions, DOM-dependent library init
componentDidUpdate(prevProps, prevState, snapshot)5 (update only)CommitYes (must guard with a prop/state comparison)Sync with external systems, conditional re-fetching
componentWillUnmount()1 (unmount only)CommitNo — instance is being discardedClear timers, close sockets, remove listeners, disconnect observers
static getDerivedStateFromError(error)1 (on descendant throw)RenderN/A — returns state objectCompute fallback UI state
componentDidCatch(error, info)2 (on descendant throw)CommitYesLog to monitoring/telemetry service

Capstone Takeaways

  • A production-grade class component composes every phase together: mount-time setup, update-time guardrails and snapshots, and unmount-time teardown all coexist in the same instance.
  • Wrapping a complex subtree in a dedicated Error Boundary at the export boundary isolates failures — a bug in the dashboard's render doesn't unmount the rest of the application shell.
  • shouldComponentUpdate and getSnapshotBeforeUpdate are what separate a merely correct class component from a performant one under real, high-frequency data (WebSocket ticks, resize events).
  • Every subscription opened in componentDidMount (socket, observer, listener) has a mirrored teardown call in componentWillUnmount — treat this pairing as non-negotiable in code review.