Server Actions

Most applications are better when you can interact with them and change the data they display. This is called "mutation."
On the web, mutations happen via the <form> element. When you submit a form, the browser sends a request to the server, which processes the data and sends back a response.
The challenge with forms is there's a pretty significant amount of indirection. The only thing connecting the form to the action it takes is the URL. If you're a developer working on a web app, finding the code that handles a form submission is often a challenge (and it's different in every app).
In React's quest to "compose all the things," React has a solution to this problem in the form of "server actions."
Server actions are a subset of server functions. Server functions are similar to client components in many ways. The RSC payload carries with it a reference to the function you wish to perform and when the form is submitted, the server gets that reference so it knows the right function to call.
To expose these server functions to be called from the client, you use the 'use server' directive. Rather than rewriting the module to not contain the function, the 'use server' directive adds reference information so the RSC payload generation can work.
When that function is called, it can be wrapped inside a startTransition which gives you a pending state for while that server function runs and that transition is pending.
To take it a step further, useActionState turns that server function into an action which resembles the way native form actions work without the URL indirection. This approach even supports progressive enhancement such that if the JavaScript has not yet been loaded, the form will act like a normal form submission.
In practice, what this all means is you get to pass a server-only function to a client component and that component can call the function when it needs to.
Here's an example of this:
'use server'

import { giveBigHug } from './utils.js'

async function hugKoala(previousState, formData) {
	const koalaName = formData.get('koalaName')
	try {
		await giveBigHug(koalaName)
		return { status: 'success' }
	} catch (error) {
		return { status: 'error', error: error.message }
	}
}
'use client'

import { useActionState } from 'react'
import { hugKoala } from './hug-koala.js'

function KoalaForm() {
	const [formState, formAction, isPending] = useActionState(hugKoala)

	return (
		<form action={formAction}>
			<label>
				Koala Name:
				<input type="text" name="koalaName" />
			</label>
			<button type="submit">{isPending ? 'Hugging...' : 'Hug Koala'}</button>
			{formState.status === 'error' ? <p>{formState.error}</p> : null}
			{formState.status === 'success' ? <p>Koala hugged!</p> : null}
		</form>
	)
}
That's all there is to it. For this to work, we need to make some client code changes as well as server code changes.

Client Changes

react-server-dom-esm is responsible for handling our server actions when they're called. To do this, we configure a callServer function when we call createFromFetch. This makes sense because the server is going to send the actions references within the payload so its at that time that we need to tell it what to do with those action references.
The callServer function will receive an id and args. The id is the server action reference and the args are the arguments that the server action needs to run (the form data).
import * as RSC from 'react-server-dom-esm/client'

function callServer(id, args) {
  const fetchPromise = fetch(`/action${getGlobalLocation()}`, {
    method: 'POST',
    headers: { 'rsc-action': id },
    body: await RSC.encodeReply(args),
  })
  // handle the fetchPromise similar to how we handle other content fetch
  // promises
}
Things get a little tricky here because this needs to be defined outside our component, but needs to update state within our component. That's an implementation detail we'll explore within the exercise.

Server Changes

The server needs to handle the action that the client is sending. This is done by parsing the rsc-action header, parsing the request body and calling the appropriate function with the given arguments.
One part of this is the need to return the result of the action to the client along with the updated RSC payload. So we change the payload from just the root element to an object with root and returnValue properties which we can then use on the client as needed.
๐Ÿ“œ Relevant Docs:
Originally, there was a single concept called "server actions" but later this was split into two concepts: "server actions" and "server functions". Server actions are a subset of server functions, specifically for form submissions.