diff --git a/exercises/04.router/01.problem.router/ui/ship-search-results.js b/exercises/04.router/01.problem.router/ui/ship-search-results.js index fb4f956..f0e3f66 100644 --- a/exercises/04.router/01.problem.router/ui/ship-search-results.js +++ b/exercises/04.router/01.problem.router/ui/ship-search-results.js @@ -14,7 +14,9 @@ export async function SearchResults() { { key: ship.name }, h( SelectShipLink, - { shipId: ship.id, highlight: ship.id === currentShipId }, + // 💣 you can remove the search prop here now that this information is + // in our router. + { shipId: ship.id, search, highlight: ship.id === currentShipId }, h(ShipImg, { src: getImageUrlForShip(ship.id, { size: 20 }), alt: ship.name, diff --git a/exercises/04.router/01.problem.router/ui/ship-search.js b/exercises/04.router/01.problem.router/ui/ship-search.js index b7c1b8c..989dd51 100644 --- a/exercises/04.router/01.problem.router/ui/ship-search.js +++ b/exercises/04.router/01.problem.router/ui/ship-search.js @@ -32,15 +32,26 @@ export function ShipSearch({ search, results, fallback }) { ) } -export function SelectShipLink({ shipId, highlight, children }) { +// 💣 you can remove the search prop here now that we can use the location from +// the router +export function SelectShipLink({ shipId, search, highlight, children }) { // 🐨 get the current location from useRouter // 🦉 the useLinkHandler you'll add in ui/index.js will set up an event handler // to listen to clicks to anchor elements and navigate properly. + + // right now we're merging manually, but now you can use our + // mergeLocationState utility. + // 🐨 update href to be mergeLocationState(location, { shipId }) + const href = [ + `/${shipId}`, + search ? `search=${encodeURIComponent(search)}` : null, + ] + .filter(Boolean) + .join('?') return h('a', { children, - // 🐨 update href to be mergeLocationState(location, { shipId }) - href: `/${shipId}`, + href, style: { fontWeight: highlight ? 'bold' : 'normal' }, }) } diff --git a/exercises/04.router/04.problem.history/README.mdx b/exercises/04.router/04.problem.history/README.mdx index 13de17e..a78773c 100644 --- a/exercises/04.router/04.problem.history/README.mdx +++ b/exercises/04.router/04.problem.history/README.mdx @@ -12,10 +12,10 @@ listen for this event and update the application state accordingly. function handlePopState() { // do stuff } -window.addEventListener(handlePopState) +window.addEventListener('popstate', handlePopState) // then to clean up -window.removeEventListener(handlePopState) +window.removeEventListener('popstate', handlePopState) ``` Please add this event listener in a `useEffect` in our router logic. diff --git a/exercises/05.actions/01.problem.action-reference/server/rsc-loader.js b/exercises/05.actions/01.problem.action-reference/server/rsc-loader.js index 875f1b2..9679650 100644 --- a/exercises/05.actions/01.problem.action-reference/server/rsc-loader.js +++ b/exercises/05.actions/01.problem.action-reference/server/rsc-loader.js @@ -20,5 +20,9 @@ export async function load(url, context, defaultLoad) { const result = await reactLoad(url, context, (u, c) => { return textLoad(u, c, defaultLoad) }) + // 💰 uncomment this to see how our loader transforms the actions file + // if (url.includes('actions.js')) { + // console.log(result.source) + // } return result } diff --git a/exercises/05.actions/01.problem.action-reference/ui/ship-details.js b/exercises/05.actions/01.problem.action-reference/ui/ship-details.js index f6b25f3..afc7653 100644 --- a/exercises/05.actions/01.problem.action-reference/ui/ship-details.js +++ b/exercises/05.actions/01.problem.action-reference/ui/ship-details.js @@ -6,6 +6,24 @@ import { EditableText } from './edit-text.js' import { getImageUrlForShip } from './img-utils.js' import { ShipImg } from './img.js' +// 💰 you can log what extra properties the updateShipName function gets because +// it's in a 'use server' file: +// const properties = {} +// for (const [key, descriptor] of Object.entries( +// Object.getOwnPropertyDescriptors(updateShipName), +// )) { +// properties[key] = descriptor.value +// } + +// console.log(updateShipName.toString()) +// console.log( +// JSON.stringify( +// properties, +// (key, value) => (typeof value === 'object' ? value : String(value)), +// 2, +// ), +// ) + export async function ShipDetails() { const { shipId } = shipDataStorage.getStore() const ship = await getShip({ shipId }) diff --git a/exercises/05.actions/01.solution.action-reference/README.mdx b/exercises/05.actions/01.solution.action-reference/README.mdx index 1395d66..9a7084d 100644 --- a/exercises/05.actions/01.solution.action-reference/README.mdx +++ b/exercises/05.actions/01.solution.action-reference/README.mdx @@ -1,6 +1,42 @@ # Action Reference -👨‍💼 Great! So this is what a reference looks like in the client: +👨‍💼 Great! So this is what our Node.js loader does to the `actions.js` module: + +``` +'use server' + +import * as db from '../db/ship-api.js' + +export async function updateShipName(previousState, formData) { + try { + await db.updateShipName({ + shipId: formData.get('shipId'), + shipName: formData.get('shipName'), + }) + return { status: 'success', message: 'Success!' } + } catch (error) { + return { status: 'error', message: error?.message || String(error) } + } +} + + +;import {registerServerReference} from "react-server-dom-esm/server"; +registerServerReference(updateShipName,"file:///Users/kentcdodds/code/epicweb-dev/react-server-components/playground/ui/actions.js","updateShipName"); +``` + +The `registerServerReference` function attaches this additional information onto +our `updateShipName` function: + +```json +{ + "$$typeof": "Symbol(react.server.reference)", + "$$id": "file:///Users/kentcdodds/code/epicweb-dev/react-server-components/playground/ui/actions.js#updateShipName", + "$$bound": null, + "bind": "function bind() {\n // $FlowFixMe[unsupported-syntax]\n var newFn = FunctionBind.apply(this, arguments);\n\n if (this.$$typeof === SERVER_REFERENCE_TAG) {\n // $FlowFixMe[method-unbinding]\n var args = ArraySlice.call(arguments, 1);\n return Object.defineProperties(newFn, {\n $$typeof: {\n value: SERVER_REFERENCE_TAG\n },\n $$id: {\n value: this.$$id\n },\n $$bound: {\n value: this.$$bound ? this.$$bound.concat(args) : args\n },\n bind: {\n value: bind\n }\n });\n }\n\n return newFn;\n}" +} +``` + +The serialized version of this function looks like this in our RSC payload: ``` d:{"id":"file:///Users/kentcdodds/code/epicweb-dev/react-server-components/playground/ui/actions.js#updateShipName","bound":null} diff --git a/exercises/05.actions/01.solution.action-reference/server/rsc-loader.js b/exercises/05.actions/01.solution.action-reference/server/rsc-loader.js index 875f1b2..5ee1a1a 100644 --- a/exercises/05.actions/01.solution.action-reference/server/rsc-loader.js +++ b/exercises/05.actions/01.solution.action-reference/server/rsc-loader.js @@ -20,5 +20,8 @@ export async function load(url, context, defaultLoad) { const result = await reactLoad(url, context, (u, c) => { return textLoad(u, c, defaultLoad) }) + if (url.includes('actions.js')) { + console.log(result.source) + } return result } diff --git a/exercises/05.actions/01.solution.action-reference/ui/ship-details.js b/exercises/05.actions/01.solution.action-reference/ui/ship-details.js index 7360aae..29d3ca9 100644 --- a/exercises/05.actions/01.solution.action-reference/ui/ship-details.js +++ b/exercises/05.actions/01.solution.action-reference/ui/ship-details.js @@ -6,6 +6,22 @@ import { EditableText } from './edit-text.js' import { getImageUrlForShip } from './img-utils.js' import { ShipImg } from './img.js' +const properties = {} +for (const [key, descriptor] of Object.entries( + Object.getOwnPropertyDescriptors(updateShipName), +)) { + properties[key] = descriptor.value +} + +console.log(updateShipName.toString()) +console.log( + JSON.stringify( + properties, + (key, value) => (typeof value === 'object' ? value : String(value)), + 2, + ), +) + export async function ShipDetails() { const { shipId } = shipDataStorage.getStore() const ship = await getShip({ shipId }) diff --git a/exercises/05.actions/03.problem.server/README.mdx b/exercises/05.actions/03.problem.server/README.mdx index e12b1ab..1831e55 100644 --- a/exercises/05.actions/03.problem.server/README.mdx +++ b/exercises/05.actions/03.problem.server/README.mdx @@ -3,6 +3,16 @@ 👨‍💼 Now that we send that reference and any arguments to the backend when we submit the form. We need an endpoint that will handle that POST request. +The POST request we're making includes the id of the action in the `rsc-action` +header: + +``` +file:///Users/kentcdodds/code/epicweb-dev/react-server-components/playground/ui/actions.js#updateShipName +``` + +We can use that to find the module and module function to call in our server +handler. + We'll be using a few server-side modules for parsing the form submission data. As this is not a Node.js/hono.js course, some of this will be given to you. You will be responsible for parsing out the action file to `import` and calling it