Mutation and Server Actions - vonschappler/Ultimate-React GitHub Wiki

What are server actions?

So far in our application all we are doing is fetching some data and rendering this data on the screen. But the main goal for any application is alway to create a fully intractive application, and on this case, because we are using Next.js it to build a full-statck interactive application, in where users can also, for example, submit data using a form in this application.

The RSC architectures is what allows us, developers, to reach this goal by:

  1. Fetching data with server components
  2. Mutating data with server Actions

Then we can say that server actions complements server components into building a full-stack interactive application, based in a single codebase, which is React.

Server actions are easily created async functions which run exclusively on the server, allowing the users to interact with data in the new RSC model.

They are created with the "user server"; directive at the top of the function definition or an entire module. Let's remember that an async function on a server component can be used by other server component or passed to a client component ulinke other functions, making them an exception to the rule that defines we can not pass functions as props to client components.

There is a better way to work of those server actions which is by creating a stand alone file (usualy named by convention as actions.js) with as many actions that we need and exporting them, so they can easly be imported into ANY component.

NOTE

The "use server" directive is used ONLY to declare server actions, not server components, because by definition, any component which does not have the "use client" directive is ALREADY a server component

We can make an analogy to help us to disguinsh both directives:

  • "use client" is the brige that createad a connection from the server to the client
  • "use server" is the other way around on the same bridge, creating the connection from the client to the server

Behind the scenes, Next.js creates an API endpoint (with its url) for each server action. When any sserver action is called/executed a post request is sent directly to the url created for that API endpoint and so the function never reaches the client, making to that the function itself always stay on the server - just the URL is sent to the client.

Because of the way server actions work, it's safe to make then use of any sort of secret keys because there is no way for the code to reach the user browser.

Unlike server components, usinge server actions imply that we require a live web server for them to work properly.

NOTES:

  1. Server actions are used to perform data mutations
  2. The way to update the UI with the new data is to revalidate the cache using revalidatePath and/or revalidateTag
  3. Work with cookies or run any code that is relevant to the action
  4. Because the code for those actions run in the background, we ALWAYS need to assume the inputs are unsafe, just like we'd do on any back end environment, allowing access to this mutations just to authorized users

Where to use Server Actions

  • Server actions are mainly used as action attributes of the <form> element in both server and client components
  • Inside client components as event handlers or inside useEffect hooks

Implementing server actions

After all the discussion above, it's then time for us to keep working on the features of our project. One of this features is to enable users to update their profiles, and so, for that, they need to be able to send information to our database.

This can EASILY performed by making use of server actions!

As stated before, there are many ways of running a server action, but the most common is by encapsulating the the components which need to answer to a given action inside a form and then calling the server action function as the action prop of the form.

import { serverActionFunction } from 'path/to/actions.js';

// other imports

export default function ComponentWithFormAction() {
  return (
    <form action={serverActionFunction}>
      {/* forms components, usually inputs and buttons*/}
    </form>
  );
}

Where serverActionFunction as an expoerted function responsible to interact with our database by creating, editing or deleting information from there.

With this basic structure it should be enough to perform any server action.

It's though, important to remember what we discussed about caching, because per nature, saver actions will turn any route into dynamic routes and then we'll have to deal with the caching.

Also let's remember we can use revalidatePath and revalidateTag if we want to force a renender with a manual cache revalidation.

With those two informations we can then work around the caching problem, by adding the snippet of code below to the serverActionFunction:

export async function serverActionFunction(formData) {
  const session = await auth();
  if (!session) throw new Error('You must be logged in to peform this action');

  // mutation logic

  revalidatePath('/path/to/revalidate');
}

Visual feedback during the execution of a server action

If we want to provide any visual feedback to the user while a server action is being executed, we can make use of a React hook, called useFormStatus.

Not that this hook has one particularity: it NEEDS to be used inside a component located inside a form and not in the component which contains the form itself.

The snippets of code below is an example of how we could make use of this hook:

// componentWithServerAction.js
'use client';

import { serverActionFunction } from 'path/to/actions.js';
import FormButton from 'path/to/formButton';

export default function ComponentWithServerAction({ children }) {
  return (
    <form action={serverActionFunction}>
      <FormButton>Click me</FormButton>
    </form>
  );
}
// formButton.js

'use client';

import { useFormStatus } from 'react-dom';

export default function FormButton({ children, handleClick }) {
  const { pending } = useFormStatus();
  return (
    <button onClick={handleClick} disabled={pending}>
      {pending ? 'Processing data...' : <>{children}</>}
    </button>
  );
}
⚠️ **GitHub.com Fallback** ⚠️