app\api\products\route.ts
Copy import { NextResponse } from "next/server";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const category = searchParams.get("category") || "all";
const sort = searchParams.get("sort") || "asc";
const products = [
{ id: 1, name: "Laptop", category: "tech" },
{ id: 2, name: "Phone", category: "tech" },
{ id: 3, name: "Shoes", category: "fashion" },
];
// Lọc theo category
const filteredProducts = category === "all" ? products : products.filter((p) => p.category === category);
// Sắp xếp
const sortedProducts = sort === "desc" ? filteredProducts.reverse() : filteredProducts;
return NextResponse.json(sortedProducts);
}
app\products\page.tsx
Copy "use client";
import { useRouter, useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
export default function ProductsPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [products, setProducts] = useState([]);
const category = searchParams.get("category") || "all";
const sort = searchParams.get("sort") || "asc";
// Gọi API khi query params thay đổi
useEffect(() => {
const fetchProducts = async () => {
const res = await fetch(`/api/products?category=${category}&sort=${sort}`);
const data = await res.json();
setProducts(data);
};
fetchProducts();
}, [category, sort]);
// Hàm cập nhật query params
const updateQueryParam = (key: string, value: string) => {
const params = new URLSearchParams(searchParams);
params.set(key, value);
router.push(`/products?${params.toString()}`, { scroll: false });
};
// Hàm reset về giá trị mặc định
const resetFilters = () => {
router.push(`/products?category=all&sort=asc`, { scroll: false });
};
const removeQueryParam = (key: string) => {
const params = new URLSearchParams(searchParams);
params.delete(key); // Xóa tham số
router.replace(`?${params.toString()}`);
};
return (
<div className="container ml-auto mr-auto gap-2">
<h1>Danh sách sản phẩm</h1>
<button onClick={() => updateQueryParam("category", "")} className="rounded-full bg-sky-500 px-5 py-2 text-sm leading-5 font-semibold text-white hover:bg-sky-700">All</button>
<button onClick={() => updateQueryParam("category", "tech")} className="rounded-full bg-sky-500 px-5 py-2 text-sm leading-5 font-semibold text-white hover:bg-sky-700">Tech</button>
<button onClick={() => updateQueryParam("category", "fashion")} className="rounded-full bg-sky-500 px-5 py-2 text-sm leading-5 font-semibold text-white hover:bg-sky-700">Fashion</button>
<button onClick={() => updateQueryParam("sort", sort === "asc" ? "desc" : "asc")} className="rounded-full bg-sky-500 px-5 py-2 text-sm leading-5 font-semibold text-white hover:bg-sky-700">
Sort {sort === "asc" ? "↓" : "↑"}
</button>
{/* Radio Buttons cho Category */}
<fieldset>
<legend>Chọn danh mục:</legend>
<label>
<input
type="radio"
name="category"
value="all"
checked={category === "all"}
onChange={() => updateQueryParam("category", "all")}
/>
Tất cả
</label>
<label>
<input
type="radio"
name="category"
value="tech"
checked={category === "tech"}
onChange={() => updateQueryParam("category", "tech")}
/>
Công nghệ
</label>
<label>
<input
type="radio"
name="category"
value="fashion"
checked={category === "fashion"}
onChange={() => updateQueryParam("category", "fashion")}
/>
Thời trang
</label>
</fieldset>
{/* Radio Buttons cho Sort */}
<fieldset>
<legend>Sắp xếp:</legend>
<label>
<input
type="radio"
name="sort"
value="asc"
checked={sort === "asc"}
onChange={() => updateQueryParam("sort", "asc")}
/>
Giá tăng dần
</label>
<label>
<input
type="radio"
name="sort"
value="desc"
checked={sort === "desc"}
onChange={() => updateQueryParam("sort", "desc")}
/>
Giá giảm dần
</label>
</fieldset>
{/* Nút Reset */}
<button onClick={resetFilters} className="rounded-full bg-sky-500 px-5 py-2 text-sm leading-5 font-semibold text-white hover:bg-sky-700">
Reset bộ lọc
</button>
<button onClick={() => removeQueryParam("category")} className="rounded-full bg-sky-500 px-5 py-2 text-sm leading-5 font-semibold text-white hover:bg-sky-700">
Xóa Category
</button>
<ul>
{products.map((product: any) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
Cách cập nhật tham số truy vấn bằng app trong nextjs
1. Sử dụng useRouter().push()
hoặc useRouter().replace()
Cập nhật toàn bộ URL
Copy tsxCopyEdit"use client";
import { useRouter } from "next/navigation";
export default function Page() {
const router = useRouter();
const updateQueryParam = () => {
router.push("?category=tech&sort=asc"); // Thay đổi toàn bộ query string
};
return <button onClick={updateQueryParam}>Cập nhật Query</button>;
}
✅ Dùng push()
: Lưu lịch sử điều hướng (có thể quay lại). ✅ Dùng replace()
: Thay thế URL hiện tại mà không thêm vào lịch sử.
2. Chỉ cập nhật một tham số truy vấn cụ thể
Cách này giúp giữ nguyên các tham số khác
Copy tsxCopyEdit"use client";
import { useRouter, useSearchParams } from "next/navigation";
export default function Page() {
const router = useRouter();
const searchParams = useSearchParams();
const updateQueryParam = (key: string, value: string) => {
const params = new URLSearchParams(searchParams);
params.set(key, value); // Cập nhật hoặc thêm tham số mới
router.push(`?${params.toString()}`, { scroll: false }); // Giữ nguyên vị trí cuộn
};
return (
<button onClick={() => updateQueryParam("category", "tech")}>
Thay đổi Category
</button>
);
}
3. Xóa một tham số truy vấn
Copy tsxCopyEdit"use client";
import { useRouter, useSearchParams } from "next/navigation";
export default function Page() {
const router = useRouter();
const searchParams = useSearchParams();
const removeQueryParam = (key: string) => {
const params = new URLSearchParams(searchParams);
params.delete(key); // Xóa tham số
router.replace(`?${params.toString()}`);
};
return (
<button onClick={() => removeQueryParam("category")}>
Xóa Category
</button>
);
}
4. Cập nhật query mà không làm mất trạng thái
Nếu bạn dùng useState
để lưu dữ liệu, sử dụng router.push()
/router.replace()
có thể gây re-render. Để tránh điều đó, dùng shallow routing :
Copy tsxCopyEditrouter.push(`?${params.toString()}`, { shallow: true });
Ví dụ:
Copy tsxCopyEditrouter.replace(`?category=tech`, { shallow: true });
🚀 Shallow routing giúp cập nhật URL mà không gọi lại getServerSideProps()
hoặc useEffect()
.
Kết luận
Dùng push()
để thêm vào lịch sử trình duyệt.
Dùng replace()
để thay thế URL hiện tại mà không lưu vào lịch sử.
Sử dụng useSearchParams()
để lấy và cập nhật query params một cách linh hoạt.
Dùng shallow: true
để tránh re-render không cần thiết.
Bạn cần cập nhật tham số truy vấn trong ngữ cảnh nào? 🚀
Cách cập nhật tham số truy vấn bằng app trong nextjs lấy ví dụ bằng api
Nếu bạn muốn cập nhật tham số truy vấn trong Next.js App Router bằng cách gọi API, bạn có thể làm như sau:
1. Tạo API Route ( app/api/products/route.ts
)
Giả sử bạn có API /api/products
để lấy danh sách sản phẩm theo category và sort order.
📌 app/api/products/route.ts
Copy tsCopyEditimport { NextResponse } from "next/server";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const category = searchParams.get("category") || "all";
const sort = searchParams.get("sort") || "asc";
const products = [
{ id: 1, name: "Laptop", category: "tech" },
{ id: 2, name: "Phone", category: "tech" },
{ id: 3, name: "Shoes", category: "fashion" },
];
// Lọc theo category
const filteredProducts =
category === "all" ? products : products.filter((p) => p.category === category);
// Sắp xếp
const sortedProducts = sort === "desc" ? filteredProducts.reverse() : filteredProducts;
return NextResponse.json(sortedProducts);
}
📌 API này sẽ trả về danh sách sản phẩm dựa trên category
và sort
.
2. Gọi API và cập nhật tham số truy vấn
📌 app/products/page.tsx
Copy tsxCopyEdit"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
export default function ProductsPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [products, setProducts] = useState([]);
const category = searchParams.get("category") || "all";
const sort = searchParams.get("sort") || "asc";
// Gọi API khi query params thay đổi
useEffect(() => {
const fetchProducts = async () => {
const res = await fetch(`/api/products?category=${category}&sort=${sort}`);
const data = await res.json();
setProducts(data);
};
fetchProducts();
}, [category, sort]);
// Hàm cập nhật query params
const updateQueryParam = (key: string, value: string) => {
const params = new URLSearchParams(searchParams);
params.set(key, value);
router.push(`/products?${params.toString()}`, { scroll: false });
};
return (
<div>
<h1>Danh sách sản phẩm</h1>
<button onClick={() => updateQueryParam("category", "tech")}>Tech</button>
<button onClick={() => updateQueryParam("category", "fashion")}>Fashion</button>
<button onClick={() => updateQueryParam("sort", sort === "asc" ? "desc" : "asc")}>
Sort {sort === "asc" ? "↓" : "↑"}
</button>
<ul>
{products.map((product: any) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
3. Giải thích
🔹 API Route (/api/products
):
Lọc danh sách sản phẩm theo category
.
Sắp xếp danh sách theo sort
.
🔹 Client-side Component :
Lấy category
và sort
từ query string bằng useSearchParams()
.
Khi category
hoặc sort
thay đổi, gọi lại API bằng useEffect()
.
Nút bấm sẽ cập nhật tham số truy vấn bằng router.push()
.
4. Kết quả
Truy cập /products
→ Hiển thị tất cả sản phẩm.
Nhấn "Tech" → URL thay đổi thành /products?category=tech
, API trả về sản phẩm tech.
Nhấn "Sort" → URL thay đổi thành /products?category=tech&sort=desc
, API trả về sản phẩm tech theo thứ tự giảm dần.
✅ Ưu điểm của cách này
✔️ Giữ trạng thái trên URL (dễ chia sẻ).
✔️ Không cần Redux hay Context để quản lý state.
✔️ Dễ dàng mở rộng với các bộ lọc khác.
Bạn có muốn thêm filter nâng cao hơn không? 🚀
Làm cho tôi thêm phần radio nữa
Dưới đây là phiên bản cập nhật của trang Products với Radio Buttons để chọn category và sort order.
📌 Cập nhật app/products/page.tsx
Copy tsxCopyEdit"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
export default function ProductsPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [products, setProducts] = useState([]);
const category = searchParams.get("category") || "all";
const sort = searchParams.get("sort") || "asc";
// Gọi API khi query params thay đổi
useEffect(() => {
const fetchProducts = async () => {
const res = await fetch(`/api/products?category=${category}&sort=${sort}`);
const data = await res.json();
setProducts(data);
};
fetchProducts();
}, [category, sort]);
// Hàm cập nhật query params
const updateQueryParam = (key: string, value: string) => {
const params = new URLSearchParams(searchParams);
params.set(key, value);
router.push(`/products?${params.toString()}`, { scroll: false });
};
return (
<div>
<h1>Danh sách sản phẩm</h1>
{/* Radio Buttons cho Category */}
<fieldset>
<legend>Chọn danh mục:</legend>
<label>
<input
type="radio"
name="category"
value="all"
checked={category === "all"}
onChange={() => updateQueryParam("category", "all")}
/>
Tất cả
</label>
<label>
<input
type="radio"
name="category"
value="tech"
checked={category === "tech"}
onChange={() => updateQueryParam("category", "tech")}
/>
Công nghệ
</label>
<label>
<input
type="radio"
name="category"
value="fashion"
checked={category === "fashion"}
onChange={() => updateQueryParam("category", "fashion")}
/>
Thời trang
</label>
</fieldset>
{/* Radio Buttons cho Sort */}
<fieldset>
<legend>Sắp xếp:</legend>
<label>
<input
type="radio"
name="sort"
value="asc"
checked={sort === "asc"}
onChange={() => updateQueryParam("sort", "asc")}
/>
Giá tăng dần
</label>
<label>
<input
type="radio"
name="sort"
value="desc"
checked={sort === "desc"}
onChange={() => updateQueryParam("sort", "desc")}
/>
Giá giảm dần
</label>
</fieldset>
<ul>
{products.map((product: any) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
🚀 Tính năng mới
✅ Radio Buttons để chọn category và sort order .
✅ Khi chọn radio, URL sẽ tự động cập nhật query params.
✅ Danh sách sản phẩm sẽ tự động lọc mà không cần tải lại trang.
Bạn muốn thêm tính năng gì nữa không? 😃
Thêm tính năng reset
Dưới đây là phiên bản cập nhật có thêm nút Reset , giúp đặt lại tất cả tham số truy vấn về mặc định (category=all
, sort=asc
).
📌 Cập nhật app/products/page.tsx
Copy tsxCopyEdit"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
export default function ProductsPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [products, setProducts] = useState([]);
const category = searchParams.get("category") || "all";
const sort = searchParams.get("sort") || "asc";
// Gọi API khi query params thay đổi
useEffect(() => {
const fetchProducts = async () => {
const res = await fetch(`/api/products?category=${category}&sort=${sort}`);
const data = await res.json();
setProducts(data);
};
fetchProducts();
}, [category, sort]);
// Hàm cập nhật query params
const updateQueryParam = (key: string, value: string) => {
const params = new URLSearchParams(searchParams);
params.set(key, value);
router.push(`/products?${params.toString()}`, { scroll: false });
};
// Hàm reset về giá trị mặc định
const resetFilters = () => {
router.push(`/products?category=all&sort=asc`, { scroll: false });
};
return (
<div>
<h1>Danh sách sản phẩm</h1>
{/* Radio Buttons cho Category */}
<fieldset>
<legend>Chọn danh mục:</legend>
<label>
<input
type="radio"
name="category"
value="all"
checked={category === "all"}
onChange={() => updateQueryParam("category", "all")}
/>
Tất cả
</label>
<label>
<input
type="radio"
name="category"
value="tech"
checked={category === "tech"}
onChange={() => updateQueryParam("category", "tech")}
/>
Công nghệ
</label>
<label>
<input
type="radio"
name="category"
value="fashion"
checked={category === "fashion"}
onChange={() => updateQueryParam("category", "fashion")}
/>
Thời trang
</label>
</fieldset>
{/* Radio Buttons cho Sort */}
<fieldset>
<legend>Sắp xếp:</legend>
<label>
<input
type="radio"
name="sort"
value="asc"
checked={sort === "asc"}
onChange={() => updateQueryParam("sort", "asc")}
/>
Giá tăng dần
</label>
<label>
<input
type="radio"
name="sort"
value="desc"
checked={sort === "desc"}
onChange={() => updateQueryParam("sort", "desc")}
/>
Giá giảm dần
</label>
</fieldset>
{/* Nút Reset */}
<button onClick={resetFilters} style={{ marginTop: "10px", padding: "5px 10px" }}>
🌀 Reset bộ lọc
</button>
<ul>
{products.map((product: any) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
🚀 Tính năng mới
✅ Thêm nút "Reset bộ lọc" , giúp đặt lại category=all
và sort=asc
.
✅ Khi nhấn Reset , URL sẽ thay đổi và danh sách sản phẩm sẽ cập nhật.
✅ Cải thiện trải nghiệm : Không cần tải lại trang.
Bạn có muốn thêm hiệu ứng loading khi gọi API không? 😊