This content originally appeared on DEV Community and was authored by Vitaliy Potapov
Did you know that Next.js Server Actions can return JSX markup instead of raw JSON data?
While it’s not explicitly mentioned in the docs, I was pleasantly surprised that it works.
Example
I have a page that renders users list with server action:
export default function Page() {
async function loadUsersAction() {
"use server";
return [
{ name: "John", age: 30 },
{ name: "Jane", age: 25 },
{ name: "Doe", age: 40 },
];
}
return <UsersList loadUsersAction={loadUsersAction} />;
}
UsersList
component loads users by button click:
export default function UsersList({ loadUsersAction }) {
const [users, setUsers] = useState();
const onClick = async () => {
const data = await loadUsersAction();
setUsers(data);
};
return (
<>
<button onClick={onClick}>Load users</button>
<ul>
{users?.map((user) => (
<li key={user.name}>
{user.name} - {user.age}
</li>
))}
</ul>
</>
);
}
Demo:
Now I change server action to return JSX with rendered users:
async function loadUsersAction() {
"use server";
const users = [
{ name: "John", age: 30 },
{ name: "Jane", age: 25 },
{ name: "Doe", age: 40 },
];
return (
<ul>
{users?.map((user) => (
<li key={user.name}>
{user.name} - {user.age}
</li>
))}
</ul>
);
}
And in UsersList
component just render server action response:
export default function UsersList({ loadUsersAction }) {
const [users, setUsers] = useState();
const onClick = async () => {
const data = await loadUsersAction();
setUsers(data);
};
return (
<>
<button onClick={onClick}>Load users</button>
{users}
</>
);
}
In browser everything works in the same way!
Note on errors handling
What if server action throws an error? When it returns a JSON data, we can catch that error inside action and return it in own format like:
{ error: "my error" }
When returning JSX, we can let error throw and catch it with the nearest error boundary on the client. Per React docs, server action calls outside the <form>
component should be wrapped into Transition for proper errors handling.
The final code of UsersList
component:
export default function UsersList({ loadUsersAction }) {
const [isPending, startTransition] = useTransition();
const [users, setUsers] = useState();
const onClick = () => {
startTransition(async () => {
const data = await loadUsersAction();
setUsers(data);
});
};
return (
<>
<button onClick={onClick}>Load users</button>
{isPending ? <div>Loading users...</div> : users}
</>
);
}
Additionally, I utilize isPending
flag to show message while loading users.
Hope it would be helpful.
Thanks for reading and happy coding!
This content originally appeared on DEV Community and was authored by Vitaliy Potapov