How to use Server Actions and client code together in Next.js

Server Actions are async functions executed on the server that can be called from Server and Client Components. There needs to be more clarity about how to call client code with Server Actions. We need more examples. In a previous post, we looked at how to clear your forms when using Server Actions. I will show you how to close a modal dialog this time by performing a server action and calling the client code.

How to close a modal dialog after a Server Action is finished

Let's look at a scenario with a modal dialog containing a form. We want to submit the form and close the modal dialog. When submitting the form, we'll call a server action. When the server action is successful, we'll close the modal.

Let's create a new dialog modal component using shadcn/ui. The modal will contain a form for creating a user:

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';
import { createUser } from '@/app/actions';

export function NewUser() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Create user</Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Create user</DialogTitle>
        </DialogHeader>

        <form action={createUser}>
          <input name='username' />
          <button type='submit'>Add user</button>
        </form>
      </DialogContent>
    </Dialog>
  )
}

Assuming the createUser function is a server action, we want to close the modal once our user is created. First, we need to pass the modal state. We'll have to make our component a Client Component to do that. Here's what the modified component will look like:

'use client'

import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';
import { createUser } from '@/app/actions';
import { useState } from 'react';

export function NewUser() {
	const [isOpen, setIsOpen] = useState(false);
	
  return (
    <Dialog open={isOpen} onOpenChange={setIsOpen}>
      <DialogTrigger asChild>
        <Button>Create user</Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Create user</DialogTitle>
        </DialogHeader>

	      <form
	        action={async (formData) => {
	          await createUser(formData);
	          setIsOpen(false);
	        }}
	      >
          <input name='username' />
          <button type='submit'>Add user</button>
        </form>
      </DialogContent>
    </Dialog>
  )
}

That's it! As simple as it can be. Mark your component with use client and call your client code as usual.

Subscribe for more like this!

Drop your email below and I'll send you new stuff to your inbox!

No spam, ever. I respect your email privacy. Unsubscribe anytime.

Read more similar articles