In almost all web apps we need to handle data fetching and data mutating:
1- Fetching Data: Which can be handled in:
I- Server Components using fetch (By default Next.js caches the returned values of fetch on the server) or with third-party libraries ,then we can pass the result as a prop to the Client Component.
import React from "react";
import { ProductCard } from "./components";
import { NoItemsFound } from "@/shared";
import { getProductsByCategory } from "@/queries";
On the server, using fetch or with third-party libraries
using Route Handler or with third-party libraries.
type ProductsProps = {
category?: string;
};
export const ProductsList = async ({ category }: ProductsProps) => {
const currentCategory = category || "all";
const products = await getProductsByCategory(currentCategory);
return (
<>
<div className="grid gap-5 justify-items-center sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 mb-[10px]">
{(products ?? []).map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
{(products ?? []).length === 0 ? <NoItemsFound /> : null}
</>
);
};
import { baseUrl } from "@/shared";
import { Products } from "@/types";
import "server-only";
export const getProductsByCategory = async (
category: string
): Promise<Products | null> => {
try {
if (category === "all") {
const res = await fetch(`${baseUrl}products`, {
next: { tags: ["getProductsByCategory"] },
});
const data = (await res.json()) as Products;
return data;
} else {
const res = await fetch(`${baseUrl}products/category/${category}`, {
next: { tags: ["getProductsByCategory"] },
});
const data = (await res.json()) as Products;
return data;
}
} catch (error) {
console.log(`Error in fetching Products`, Error);
return null;
}
};
II- Client Components using Route Handler or with third-party libraries.
2- Mutating Data: Which is recommedned to be handled using Server Actions on both Server and Client Components:
Server Actions represent a significant improve in Next.js. Which are asynchronous functions and can be securely invoked, directly from React (Server or Client) Components.
In Server Components:
We can use both inline function level or module level "use server" directive.
In Client Components:
To use Server Action in a Client Component, we can simply create a new file and add the "use server" directive at the top of it (module-level "use server" directive). Then all functions within the file will be marked as Server Actions that can be reused in both Client and Server Components.
- Before server actions, we need to use react-query or SWR to perform mutations effectively. But by using server actions, we can simply create a file, then using "use server" directive, writing a function to perform the our target goal (By communicating with the database), and then we can invoke them in from form, event handlers, useEffect, third-party libraries.
With this simplicity in writing Server Actions, we can achieve not only code cleanliness but also reusability. By writing Server action to handle specific functionality the using it across different components.
With Server Actions developers need to write less boilerplate code. Unlike API routes which required manual setup and invocation, Server Actions are defined within the same context as your components.
Even though you can use server actions to fetch data, Next.js discourages it because it can only send a POST request. Instead, we can fetch the data from the server component and pass it down as props.
Invoking Server Action by submitting a form:
const onSubmit: SubmitHandler<UserSchemaType> = async (formData) => {
const response = await createNewUser(formData);
if ([400, 500].includes(response.status)) {
toast.error(response.message);
} else {
toast.success(response.message);
reset();
}
};
🎉🎉 [Project on GitHub]: Nextjs 14 with App Router, Register a new user, Send Confirmation email, Reset password, Forget password ... (https://github.com/alaa-m1/nextjs14-approuter-nextauth-shopsfinder)
Top comments (0)