Work Hours
Monday to Friday: 8am – 6pm
Weekend: 10am – 2pm
In this chapter, we will focus on fetching data in two ways: Server Action and API. We also touch forms topic.
Next.js defines server actions as asynchronous functions that are executed at server side. The real power of this technique is handling at server side, but we can call it from both, server and client sides. This will be used to retrieve data from internal processes as internal API or database.
Implementing a server action is as simple as mark a code block with 'use server'
header and implement it as async function:
export const handleForm = async (formData) => {
"user server"
const { xxx } = Object.fromEntries(formData);
}
Using is even simpler:
<form action={handleForm}>
...
</form>
Standard way to fetch data from another server (or same) is using fetch
.
Next.js extends the implementation of fetch, adding caching. This is crucial to get most of the caching knowledge in next.js, especially it handles a little bit different in dev and production environment. Production is using caching and dev environment not always. It can lead to strange behavior.
Good explanation is, as always, in documentation.
An example of using fetch is simple:
async function getData() {
const res = await fetch('https://api.example.com/...')
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
if (!res.ok) {
// This will activate the closest `error.js` Error Boundary
throw new Error('Failed to fetch data')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return <main></main>
}
It is simple. Always, when you can, use server action.
Especially, all database, files and other server side data are easily accessible via server action. This way is also faster – retrieve data and rendering is at server side. It is more secure – we do not send any tokens or authorizations between client and server.
There are a few rules worth to mention:
revalidatePath
when use server action.If we are handling data topic, it would be a good place to talk about forms a little.
Next.js, of course, extends standard form
element. It allows using action
property and server action to handle the data. Placing form creates FormData object. We do not need useState anymore. We can simply get data using get method.
export const addNewPost = async (
previousState: any,
formData: FormData
): Promise<FormResult | null | undefined> => {
try {
const errors = validateAddNewPostData(formData);
if (errors != null) {
return errors;
}
const rawFormData: InsertPost = {
id: uuidv4(),
title: formData.get('title')?.toString() ?? '',
date: new Date().toISOString().substring(0, 10),
image: formData.get('image')?.toString() ?? '',
content: formData.get('content')?.toString() ?? '',
};
addPost(rawFormData);
revalidatePath('/posts');
return { success: true };
} catch (e) {
console.log(e);
throw new Error('There was an error while adding new post');
}
};
In practice, when we need more advanced validation than stock, we are ending with client side form and server side action to store data.
In server component with placeholder for form, we just put client side component:
export default function PageAdd() {
return (
<div>
<PostForm />
</div>
);
}
'use client';
{...}
function PostForm() {
const [state, formAction] = useFormState(addNewPost, undefined);
const { pending } = useFormStatus();
const router = useRouter();
useEffect(() => {
state?.success && router.push('/');
}, [state?.success, router]);
return (
<form action={formAction}>
{...}
<div className="flex items-center justify-end">
<button
type="submit"
aria-disabled={pending}
>
Add post
</button>
</div>
</form>
);
}
export default PostForm;
We can also use useFormStatus
hook to retrieve status and disable button for processing time.
In the next chapter, I will write about publishing, with one interesting useage. Stay tuned.
The whole example project can be found here.