|
| 1 | +# React Server Components Workshop - Learning Review |
| 2 | + |
| 3 | +## Overview |
| 4 | +This document captures my experience completing the React Server Components workshop by Kent C. Dodds on EpicReact.dev. |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Exercise 01: Warm Up |
| 9 | + |
| 10 | +### Exercise 01.01: Static React App |
| 11 | + |
| 12 | +**Objective:** Set up a basic hono.js server to serve static assets and a data API endpoint. |
| 13 | + |
| 14 | +**Solution diff notes:** My solution matched the official solution functionally. Only differences were: |
| 15 | +- Comments I left in the code (non-harmful) |
| 16 | +- Minor parentheses formatting around arrow function parameters |
| 17 | + |
| 18 | +**Feedback:** no notes. |
| 19 | + |
| 20 | +The exercise successfully introduces the application codebase and hono.js server setup without overwhelming with React Server Components concepts too early. The "hand-holdy" approach with commented-out solution code makes this appropriate as a warm-up. |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## Exercise 02: Server Components |
| 25 | + |
| 26 | +### Exercise 02.01: RSCs |
| 27 | + |
| 28 | +**Objective:** Implement React Server Components by using `react-server-dom-esm` to generate serialized JSX on the server and render it in the browser. |
| 29 | + |
| 30 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 31 | + |
| 32 | +**Feedback:** no notes. |
| 33 | + |
| 34 | +The exercise excellently explains the `react-server` export condition concept and why RSCs need a special environment. The step-by-step approach (package.json → import map → server → client) creates a clear mental model of how RSC data flows through the system. |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +### Exercise 02.02: Async Components |
| 39 | + |
| 40 | +**Objective:** Refactor components to use async/await for data loading instead of receiving data as props from the server route. |
| 41 | + |
| 42 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 43 | + |
| 44 | +**Feedback:** no notes. |
| 45 | + |
| 46 | +This exercise clearly demonstrates the benefit of RSCs - components can load their own data with async/await. The transformation from "data-as-props" to "data-in-component" is straightforward and impactful. |
| 47 | + |
| 48 | +--- |
| 49 | + |
| 50 | +### Exercise 02.03: Streaming |
| 51 | + |
| 52 | +**Objective:** Add Suspense boundaries around async server components to enable granular loading states and out-of-order streaming. |
| 53 | + |
| 54 | +**Solution diff notes:** My solution matched with only trivial import ordering difference (`Suspense, Fragment` vs `Fragment, Suspense`). |
| 55 | + |
| 56 | +**Feedback:** no notes. |
| 57 | + |
| 58 | +The exercise effectively demonstrates how Suspense works with RSCs for streaming. The pre-built fallback components (ShipFallback, SearchResultsFallback) minimize boilerplate and keep focus on the Suspense concept. |
| 59 | + |
| 60 | +--- |
| 61 | + |
| 62 | +### Exercise 02.04: Server Context |
| 63 | + |
| 64 | +**Objective:** Use Node.js `AsyncLocalStorage` to eliminate prop drilling for `search` and `shipId` values across server components. |
| 65 | + |
| 66 | +**Solution diff notes:** My solution matched with only cosmetic formatting differences (multi-line vs single-line h() calls, import ordering). |
| 67 | + |
| 68 | +**Feedback:** no notes. |
| 69 | + |
| 70 | +Excellent introduction to `AsyncLocalStorage` as a replacement for React Context in RSC environments. The explanation of why Context doesn't work in RSCs and the recommended alternative is valuable knowledge. |
| 71 | + |
| 72 | +--- |
| 73 | + |
| 74 | +## Exercise 03: Client Components |
| 75 | + |
| 76 | +### Exercise 03.01: Node.js Loader |
| 77 | + |
| 78 | +**Objective:** Register a Node.js custom loader to handle `'use client'` modules and transform their exports into reference registrations. |
| 79 | + |
| 80 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 81 | + |
| 82 | +**Feedback:** no notes. |
| 83 | + |
| 84 | +The exercise effectively demonstrates how the RSC bundler handles `'use client'` directives through Node.js custom loaders. The console.log debugging approach helps visualize what the server sees for client components. |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +### Exercise 03.02: Module Resolution |
| 89 | + |
| 90 | +**Objective:** Configure `renderToPipeableStream` and `createFromFetch` with module base paths so client component references are properly resolved. |
| 91 | + |
| 92 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 93 | + |
| 94 | +**Feedback:** no notes. |
| 95 | + |
| 96 | +Clear explanation of the server-side and client-side module resolution requirements. The warning about esm.sh and needing full URLs is helpful context. |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## Exercise 04: Client Router |
| 101 | + |
| 102 | +### Exercise 04.01: Client Router |
| 103 | + |
| 104 | +**Objective:** Implement client-side navigation to avoid full page refreshes when users search and select ships. |
| 105 | + |
| 106 | +**Solution diff notes:** My solution matched exactly - no differences after aligning with minor formatting conventions. |
| 107 | + |
| 108 | +**Feedback:** no notes. |
| 109 | + |
| 110 | +Good introduction to building a custom router for RSC apps. The pre-built `useLinkHandler` utility and `mergeLocationState` helper keep focus on the navigation logic rather than boilerplate. Clear explanation of `pushState` vs `replaceState` usage. |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +### Exercise 04.02: Pending UI |
| 115 | + |
| 116 | +**Objective:** Implement pending UI states using `useTransition`, `useDeferredValue`, and show visual feedback during navigation. |
| 117 | + |
| 118 | +**Solution diff notes:** My solution matched exactly after including all required changes (ShipDetailsPendingTransition, useSpinDelay for extra credit). |
| 119 | + |
| 120 | +**Feedback:** no notes. |
| 121 | + |
| 122 | +Excellent exercise on pending states. The approach of using `useDeferredValue` to keep the old location until the transition completes is elegant. The extra credit with `useSpinDelay` to avoid flash of loading state is a nice UX touch. |
| 123 | + |
| 124 | +--- |
| 125 | + |
| 126 | +### Exercise 04.03: Race Conditions |
| 127 | + |
| 128 | +**Objective:** Prevent out-of-order navigation updates using a ref to track the latest navigation request. |
| 129 | + |
| 130 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 131 | + |
| 132 | +**Feedback:** no notes. |
| 133 | + |
| 134 | +Simple but effective pattern using Symbol and a ref to handle race conditions. The explanation is clear and the test scenario (simulated delay for "st" search) helps verify the fix works. |
| 135 | + |
| 136 | +--- |
| 137 | + |
| 138 | +### Exercise 04.04: History |
| 139 | + |
| 140 | +**Objective:** Handle browser back/forward buttons by listening to the `popstate` event. |
| 141 | + |
| 142 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 143 | + |
| 144 | +**Feedback:** no notes. |
| 145 | + |
| 146 | +Straightforward exercise on handling browser history navigation. Clear explanation of the popstate event and proper cleanup in useEffect. |
| 147 | + |
| 148 | +--- |
| 149 | + |
| 150 | +### Exercise 04.05: Cache |
| 151 | + |
| 152 | +**Objective:** Implement content caching using `window.history.state` to enable instant back/forward navigation without refetching. |
| 153 | + |
| 154 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 155 | + |
| 156 | +**Feedback:** no notes. |
| 157 | + |
| 158 | +Clever use of `window.history.state` as a cache key. The ObservableMap pattern with `useSyncExternalStore` is elegant for triggering re-renders on cache updates. |
| 159 | + |
| 160 | +--- |
| 161 | + |
| 162 | +## Exercise 05: Server Actions |
| 163 | + |
| 164 | +### Exercise 05.01: Action Reference |
| 165 | + |
| 166 | +**Objective:** Create a server action with `'use server'` directive and wire it up to a client component using `useActionState`. |
| 167 | + |
| 168 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 169 | + |
| 170 | +**Feedback:** no notes. |
| 171 | + |
| 172 | +Good introduction to server actions. The debug logging helps visualize how the RSC loader transforms `'use server'` modules into references. Clear warning about `react-server-dom-esm` form handling. |
| 173 | + |
| 174 | +--- |
| 175 | + |
| 176 | +### Exercise 05.02: Client Side |
| 177 | + |
| 178 | +**Objective:** Implement the `callServer` function to handle action calls from the client, sending them to the server via POST request. |
| 179 | + |
| 180 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 181 | + |
| 182 | +**Feedback:** no notes. |
| 183 | + |
| 184 | +Clear explanation of how `callServer` works with `createFromFetch`. The use of `RSC.encodeReply` for serializing arguments is an important detail. |
| 185 | + |
| 186 | +--- |
| 187 | + |
| 188 | +### Exercise 05.03: Server Side |
| 189 | + |
| 190 | +**Objective:** Handle server action POST requests by parsing the action reference, importing the action function, and executing it. |
| 191 | + |
| 192 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 193 | + |
| 194 | +**Feedback:** no notes. |
| 195 | + |
| 196 | +Good coverage of the server-side action handling. The validation of `$$typeof` for server references is an important security consideration that's explicitly called out. |
| 197 | + |
| 198 | +--- |
| 199 | + |
| 200 | +### Exercise 05.04: Revalidation |
| 201 | + |
| 202 | +**Objective:** Update the UI after server actions by caching the new RSC payload and triggering a re-render when the stream completes. |
| 203 | + |
| 204 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 205 | + |
| 206 | +**Feedback:** no notes. |
| 207 | + |
| 208 | +This is a complex but well-explained exercise. The `onStreamFinished` utility pattern for waiting on stream completion is clever. The explanation of why we need to reassign `updateContentKey` from within the component is clear. |
| 209 | + |
| 210 | +--- |
| 211 | + |
| 212 | +### Exercise 05.05: History Revalidation |
| 213 | + |
| 214 | +**Objective:** Revalidate cached content when navigating back/forward to ensure up-to-date data after server actions. |
| 215 | + |
| 216 | +**Solution diff notes:** My solution matched exactly - no differences. |
| 217 | + |
| 218 | +**Feedback:** no notes. |
| 219 | + |
| 220 | +Nice culmination of the workshop. The approach of always fetching new content on popstate while using the cached version initially (if available) provides the best UX. |
| 221 | + |
| 222 | +--- |
| 223 | + |
| 224 | +## Workshop Summary |
| 225 | + |
| 226 | +**Overall Assessment:** Excellent workshop on React Server Components. |
| 227 | + |
| 228 | +**Strengths:** |
| 229 | +- Progressive complexity - each exercise builds naturally on the previous |
| 230 | +- Clear explanations of RSC concepts (server vs client components, streaming, actions) |
| 231 | +- Practical patterns (AsyncLocalStorage, content caching, race condition handling) |
| 232 | +- Good balance between hand-holding early on and more independent work later |
| 233 | + |
| 234 | +**All exercises completed successfully with solutions matching the official solutions.** |
| 235 | + |
0 commit comments