🪟 Windows TippsModify Windows Support Phone Number with PowerShell(03.09.2026 um 00:00 Uhr)
🔧 AI Nachrichten Podcast: ChatGPT schwatzt Nutzern in Deutschland jetzt Werbung auf(28.08.2026 um 08:46 Uhr)
🪟 Windows TippsMicrosoft bringt Emoji 17.0 auf Windows 11(31.08.2026 um 08:16 Uhr)
🪟 Windows TippsModify Windows Support Phone Number with PowerShell(03.09.2026 um 00:00 Uhr)
🔧 AI Nachrichten Podcast: ChatGPT schwatzt Nutzern in Deutschland jetzt Werbung auf(28.08.2026 um 08:46 Uhr)
🪟 Windows TippsMicrosoft bringt Emoji 17.0 auf Windows 11(31.08.2026 um 08:16 Uhr)

🔧 Programmierung 🕛 vor 1 Jahr 12 Min Lesezeit
0

Next.js API Management: Implement Search, Pagination, Filter, Sort, and Limit Features

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

In this article, I want to share how I handle APIs in a Next.js application, including fetching data, displaying it, and implementing features such as searching, pagination, filtering, sorting, and limiting results. Let's dive straight into the details.






Requirements:




  • Next.js 14.2.18

  • Axios 1.7.8

  • Use debounce 10.0.4

  • API, Make sure the API you use supports features like searching, pagination, filtering, sorting, and limiting data. In this article, I'll demonstrate using the






    Fetch and Display Data






    CODE
    //src/lib/actions.ts

    import axios from 'axios';

    const axiosInstance = axios.create({
    baseURL: 'https://api.jikan.moe/v4',
    });

    export const getAllAnime = async (
    query?: string,
    currentPage?: number,
    orderBy?: string,
    type?: string,
    status?: string,
    sort?: string,
    limit?: number
    ) => {
    try {
    const res = await axiosInstance.get(
    `/anime?q=${query}&page=${currentPage}&order_by=${orderBy}&type=${type}&status=${status}&sort=${sort}&limit=${limit}`
    );

    const totalPages = res.data.pagination.last_visible_page;

    return {
    data: res.data.data,
    totalPages,
    };
    } catch (error) {
    console.log(error);
    return null;
    }
    };









    CODE
    //src/components/AnimeList.tsx

    import { getAllAnime } from '@/lib/actions';
    import Image from 'next/image';

    type AnimeProps = {
    mal_id: number;
    images: {
    webp: {
    image_url: string;
    };
    };
    title: string;
    };

    export default async function AnimeList({
    query,
    currentPage,
    orderBy,
    type,
    status,
    sort,
    limit,
    }: {
    query?: string;
    currentPage?: number;
    orderBy?: string;
    type?: string;
    status?: string;
    sort?: string;
    limit?: number;
    }) {
    const animes = await getAllAnime(
    query,
    currentPage,
    orderBy,
    type,
    status,
    sort,
    limit
    );

    return (
    <>
    <div className="grid grid-cols-6 gap-4 pt-4">
    {animes?.data.map((anime: AnimeProps) => (
    <div key={anime.mal_id}>
    <Image
    src={anime.images.webp.image_url}
    alt={anime.title}
    width={500}
    height={500}
    className="w-full h-[250px] object-cover rounded-md"
    />
    <h3>
    {anime.title.length > 15
    ? `${anime.title.slice(0, 15)}...`
    : anime.title}
    </h3>
    </div>
    ))}
    </div>
    </>
    );
    }









    Implement Search






    CODE
    //src/components/Search.tsx

    'use client';

    import { usePathname, useRouter, useSearchParams } from 'next/navigation';
    import { useDebouncedCallback } from 'use-debounce';

    export default function Search() {
    const pathname = usePathname();
    const { replace } = useRouter();
    const searchParams = useSearchParams();

    const handleSearch = useDebouncedCallback((term: string) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', '1');
    if (term) {
    params.set('query', term);
    } else {
    params.delete('query');
    }
    replace(`${pathname}?${params.toString()}`);
    }, 500);

    return (
    <div className="w-[50%]">
    <input
    type="text"
    placeholder="Search..."
    className="w-full bg-neutral-200 px-2 py-1 rounded-md"
    onChange={(e) => {
    handleSearch(e.target.value);
    }}
    defaultValue={searchParams.get('query')?.toString()}
    />
    </div>
    );
    }









    Implement Pagination






    CODE
    //src/components/Pagination.tsx

    'use client';

    import { usePathname, useRouter, useSearchParams } from 'next/navigation';

    export default function Pagination({ totalPages }: { totalPages: number }) {
    const pathname = usePathname();
    const searchParams = useSearchParams();
    const currentPage = Number(searchParams.get('page')) || 1;
    const router = useRouter();

    const createPageURL = (pageNumber: number | string) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', pageNumber.toString());
    return `${pathname}?${params.toString()}`;
    };

    return (
    <>
    <div className="w-full flex items-center justify-center gap-x-8">
    <button
    onClick={() => router.push(createPageURL(currentPage - 1))}
    disabled={currentPage === 1}
    className="bg-neutral-600 px-2 py-1 rounded-md text-neutral-100 disabled:bg-neutral-400 disabled:cursor-not-allowed"
    >
    Prev
    </button>
    <p className="text-[1rem] text-neutral-700">
    Page&nbsp;{currentPage} of&nbsp;{totalPages}
    </p>
    <button
    onClick={() => router.push(createPageURL(currentPage + 1))}
    disabled={currentPage === totalPages}
    className="bg-neutral-600 px-2 py-1 rounded-md text-neutral-100 disabled:bg-neutral-400 disabled:cursor-not-allowed"
    >
    Next
    </button>
    </div>
    </>
    );
    }









    Implement Filter and Sort






    CODE
    //src/components/FilterByType.tsx

    'use client';

    import { usePathname, useRouter, useSearchParams } from 'next/navigation';

    type TypeProps = { value: string; name: string };

    const TYPE = [
    { value: 'tv', name: 'TV' },
    { value: 'movie', name: 'Movie' },
    { value: 'ova', name: 'OVA' },
    { value: 'special', name: 'Special' },
    { value: 'ona', name: 'ONA' },
    { value: 'music', name: 'Music' },
    { value: 'cm', name: 'CM' },
    { value: 'pv', name: 'PV' },
    { value: 'tv_special', name: 'TV Special' },
    ];

    export default function FilterByType() {
    const pathname = usePathname();
    const searchParams = useSearchParams();
    const router = useRouter();

    const handleFilterByType = (type: string) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', '1');
    params.set('type', type);
    router.replace(`${pathname}?${params.toString()}`);
    };

    return (
    <>
    <form className="w-[10%] bg-neutral-400 px-2 py-1 rounded-md">
    <select
    className="bg-neutral-400 w-full"
    onChange={(e) => handleFilterByType(e.target.value)}
    defaultValue={searchParams.get('type')?.toString()}
    >
    <option value={''} defaultValue={''}>
    Type:
    </option>
    {TYPE.map((type: TypeProps, index) => (
    <option key={index} value={type.value}>
    {type.name}
    </option>
    ))}
    </select>
    </form>
    </>
    );
    }









    CODE
    //src/components/FilterByStatus.tsx

    'use client';

    import { usePathname, useRouter, useSearchParams } from 'next/navigation';

    type StatusProps = { value: string; name: string };

    const STATUS = [
    { value: 'airing', name: 'Airing' },
    { value: 'upcoming', name: 'Upcoming' },
    { value: 'complete', name: 'Complete' },
    ];

    export default function FilterByStatus() {
    const pathname = usePathname();
    const searchParams = useSearchParams();
    const router = useRouter();

    const handleFilterByStatus = (status: string) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', '1');
    params.set('status', status);
    router.replace(`${pathname}?${params.toString()}`);
    };
    return (
    <>
    <form className="w-[10%] bg-neutral-400 px-2 py-1 rounded-md">
    <select
    className="bg-neutral-400 w-full"
    onChange={(e) => handleFilterByStatus(e.target.value)}
    defaultValue={searchParams.get('status')?.toString()}
    >
    <option value={''} defaultValue={''}>
    Status:
    </option>
    {STATUS.map((status: StatusProps, index) => (
    <option key={index} value={status.value}>
    {status.name}
    </option>
    ))}
    </select>
    </form>
    </>
    );
    }









    CODE
    //src/components/SortByOrder

    'use client';

    import { usePathname, useRouter, useSearchParams } from 'next/navigation';

    type OrderByProps = { value: string; name: string };

    const ORDER_BY = [
    { value: 'mal_id', name: 'Mal id' },
    { value: 'title', name: 'Title' },
    { value: 'start_date', name: 'Start date' },
    { value: 'end_date', name: 'End date' },
    { value: 'episodes', name: 'Episodes' },
    { value: 'score', name: 'Score' },
    { value: 'scored_by', name: 'Scored by' },
    { value: 'rank', name: 'Rank' },
    { value: 'popularity', name: 'Popularity' },
    { value: 'members', name: 'Members' },
    { value: 'favorites', name: 'Favorites' },
    ];

    export default function SortByOrder() {
    const pathname = usePathname();
    const searchParams = useSearchParams();
    const router = useRouter();

    const handleSortByOrder = (orderBy: string) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', '1');
    params.set('order_by', orderBy);
    router.replace(`${pathname}?${params.toString()}`);
    };
    return (
    <>
    <form className="w-[10%] bg-neutral-400 px-2 py-1 rounded-md">
    <select
    className="bg-neutral-400 w-full"
    onChange={(e) => handleSortByOrder(e.target.value)}
    defaultValue={searchParams.get('order_by')?.toString()}
    >
    <option value={''} defaultValue={''}>
    Order by:
    </option>
    {ORDER_BY.map((orderBy: OrderByProps, index: number) => (
    <option key={index} value={orderBy.value}>
    {orderBy.name}
    </option>
    ))}
    </select>
    </form>
    </>
    );
    }









    CODE
    //src/components/SortDirection.tsx

    'use client';

    import { usePathname, useRouter, useSearchParams } from 'next/navigation';

    type SortDirectionProps = { value: string; name: string };

    const SORT_DIRECTION = [
    { value: 'asc', name: 'Ascending' },
    { value: 'desc', name: 'Descending' },
    ];

    export default function SortDirection() {
    const pathname = usePathname();
    const searchParams = useSearchParams();
    const router = useRouter();

    const handleSortDirection = (sort: string) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', '1');
    params.set('sort', sort);
    router.replace(`${pathname}?${params.toString()}`);
    };
    return (
    <>
    <form className="w-[10%] bg-neutral-400 px-2 py-1 rounded-md">
    <select
    className="bg-neutral-400 w-full"
    onChange={(e) => handleSortDirection(e.target.value)}
    defaultValue={searchParams.get('sort')?.toString()}
    >
    <option value={''} defaultValue={''}>
    Sort:
    </option>
    {SORT_DIRECTION.map((orderBy: SortDirectionProps, index: number) => (
    <option key={index} value={orderBy.value}>
    {orderBy.name}
    </option>
    ))}
    </select>
    </form>
    </>
    );
    }









    Limiting Results






    CODE
    //src/components/Limit.tsx

    'use client';

    import { usePathname, useRouter, useSearchParams } from 'next/navigation';

    export default function Limit() {
    const pathname = usePathname();
    const searchParams = useSearchParams();
    const router = useRouter();

    const handleLimitChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const value = event.target.value;
    const params = new URLSearchParams(searchParams);

    if (!isNaN(Number(value)) && Number(value) > 0) {
    params.set('page', '1');
    params.set('limit', value);
    router.replace(`${pathname}?${params.toString()}`);
    }

    if (
    Number(value) === 0 ||
    isNaN(Number(value)) ||
    value === '' ||
    Number(value) > 25
    ) {
    params.delete('limit');
    router.replace(`${pathname}?${params.toString()}`);
    }
    };

    return (
    <form className="w-[10%] bg-neutral-400 px-2 py-1 rounded-md flex items-center gap-x-2">
    <label htmlFor="limit">Limit:</label>
    <input
    id="limit"
    type="string"
    className="bg-neutral-400 w-full placeholder-neutral-700"
    min="1"
    placeholder="1-25"
    defaultValue={searchParams.get('limit') || ''}
    onChange={handleLimitChange}
    />
    </form>
    );
    }









    Home Page






    CODE
    //src/app/page

    import AnimeList from '@/components/AnimeList';
    import FilterByStatus from '@/components/FilterByStatus';
    import FilterByType from '@/components/FilterByType';
    import Limit from '@/components/Limit';
    import Pagination from '@/components/Pagination';
    import Search from '@/components/Search';
    import SortByOrder from '@/components/SortByOrder';
    import SortDirection from '@/components/SortDirection';
    import { getAllAnime } from '@/lib/actions';
    import { Suspense } from 'react';

    export default async function Home({
    searchParams,
    }: {
    searchParams?: {
    query?: string;
    page?: string;
    order_by?: string;
    type?: string;
    status?: string;
    sort?: string;
    limit?: string;
    };
    }) {
    const query = searchParams?.query || '';
    const currentPage = Number(searchParams?.page) || 1;
    const orderBy = searchParams?.order_by || '';
    const type = searchParams?.type || '';
    const status = searchParams?.status || '';
    const sort = searchParams?.sort || '';
    const limit = Number(searchParams?.limit) || 25;

    const pages = await getAllAnime(
    query,
    currentPage,
    orderBy,
    type,
    status,
    sort,
    limit
    );
    const totalPages = pages?.totalPages;

    return (
    <main className="w-[1020px] mx-auto">
    <div className="flex items-center gap-x-4 py-4">
    <Search />
    <FilterByType />
    <FilterByStatus />
    <SortByOrder />
    <SortDirection />
    <Limit />
    </div>
    <Pagination totalPages={totalPages} />
    <Suspense
    key={query + currentPage + orderBy + type + status + sort + limit}
    fallback={<div>Loading...</div>}
    >
    <AnimeList
    query={query}
    currentPage={currentPage}
    orderBy={orderBy}
    type={type}
    status={status}
    sort={sort}
    limit={limit}
    />
    </Suspense>
    </main>
    );
    }









    Refactor Components



    To make the code more concise and reusable, we can refactor the filter and sort components to reduce redundancy. Use FilterSelect.tsx component that can handle both the filtering and sorting functionality. Here's how you can refactor your components:




    CODE
    //src/components/FilterSelect.tsx

    'use client';

    import { usePathname, useRouter, useSearchParams } from 'next/navigation';
    import React from 'react';

    type FilterSelectProps = {
    options: { value: string; name: string }[];
    paramKey: string;
    label: string;
    };

    const FilterSelect: React.FC<FilterSelectProps> = ({
    options,
    paramKey,
    label,
    }) => {
    const pathname = usePathname();
    const searchParams = useSearchParams();
    const router = useRouter();

    const handleChange = (value: string) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', '1');
    params.set(paramKey, value);
    router.replace(`${pathname}?${params.toString()}`);
    };

    return (
    <form className="w-[10%] bg-neutral-400 px-2 py-1 rounded-md">
    <select
    className="bg-neutral-400 w-full"
    onChange={(e) => handleChange(e.target.value)}
    defaultValue={searchParams.get(paramKey) || ''}
    >
    <option value="">{label}:</option>
    {options.map((option, index) => (
    <option key={index} value={option.value}>
    {option.name}
    </option>
    ))}
    </select>
    </form>
    );
    };

    const STATUS = [
    { value: 'airing', name: 'Airing' },
    { value: 'upcoming', name: 'Upcoming' },
    { value: 'complete', name: 'Complete' },
    ];

    const TYPE = [
    { value: 'tv', name: 'TV' },
    { value: 'movie', name: 'Movie' },
    { value: 'ova', name: 'OVA' },
    { value: 'special', name: 'Special' },
    { value: 'ona', name: 'ONA' },
    { value: 'music', name: 'Music' },
    { value: 'cm', name: 'CM' },
    { value: 'pv', name: 'PV' },
    { value: 'tv_special', name: 'TV Special' },
    ];

    const ORDER_BY = [
    { value: 'mal_id', name: 'Mal id' },
    { value: 'title', name: 'Title' },
    { value: 'start_date', name: 'Start date' },
    { value: 'end_date', name: 'End date' },
    { value: 'episodes', name: 'Episodes' },
    { value: 'score', name: 'Score' },
    { value: 'scored_by', name: 'Scored by' },
    { value: 'rank', name: 'Rank' },
    { value: 'popularity', name: 'Popularity' },
    { value: 'members', name: 'Members' },
    { value: 'favorites', name: 'Favorites' },
    ];

    const SORT_DIRECTION = [
    { value: 'asc', name: 'Ascending' },
    { value: 'desc', name: 'Descending' },
    ];

    export function FilterByStatus() {
    return <FilterSelect options={STATUS} paramKey="status" label="Status" />;
    }

    export function FilterByType() {
    return <FilterSelect options={TYPE} paramKey="type" label="Type" />;
    }

    export function SortByOrder() {
    return (
    <FilterSelect options={ORDER_BY} paramKey="order_by" label="Order by" />
    );
    }

    export function SortDirection() {
    return <FilterSelect options={SORT_DIRECTION} paramKey="sort" label="Sort" />;
    }






    Modify home page and import filter and sort components from FilterSelect






    Explanation



    usePathname, useSearchParams, and useRouterHooks

    These hooks from next/navigation are essential for managing URL states:





    • usePathname: Retrieves the current path of the URL.


    • useSearchParams: Accesses and manipulates query parameters.


    • useRouter: Provides routing functions to programmatically navigate or update URLs (replace or push).



    debouncedCallback in Search Component

    The useDebouncedCallback function delays the execution of the search request:




    • Prevents excessive API calls by waiting for a specified duration (500ms).

    • Ensures better performance and smoother user experience.



    Dynamic URL Updates

    Dynamic updates to URL parameters ensure a smooth, client-side navigation:





    • replace() updates the URL without reloading the page.

    • Keeps the browser history clean while reflecting the filter, sort, and pagination changes.



    AnimeList Component and Data Fetching

    The AnimeList component handles:




    • Asynchronous data fetching from the Jikan API (getAllAnime()).



    Reusable FilterSelect Component

    The FilterSelectcomponent:




    • Abstracts dropdown functionality for filters like status, type, order_by, and sort.

    • Simplifies code by reducing redundancy across filterand sortcomponents.



    Pagination Handling

    The Pagination component:




    • Dynamically generates navigation links (Prev and Next) based on currentPage and totalPages.

    • Disables buttons when reaching the first or last page.



    Limit Input Validation

    The Limit component validates user input for limiting the number of results:




    • Ensures the value is between 1 and 25.

    • Updates the URL dynamically or resets if the input is invalid.



    Suspense for Data Fetching

    The Suspense component is used to handle asynchronous rendering:




    • Displays a fallback loading indicator while waiting for data.

    • Ensures the UI remains responsive during data fetches.



    and this is how the app looks like:





    Conclusion

    I hope this article has provided a comprehensive guide on how to implement essential features like search, pagination, filtering, sorting, and result limiting in a Next.js application.



    Feel free to explore, clone, or contribute to the project on

    Vollständiger Original-Bericht
    Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
    ↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Modify Windows Support Phone Number with PowerShell
1 Quelle
Die Zukunft des Einkaufens: Warum wir ein neues Kapitel aufschlagen (und wie du es mitschreiben kannst)
1 Quelle
ZDE Podcast 251: Wie sieht digitales Instore Marketing 2026 aus, Amit Chatterjee?
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Next.js API Management: Implement Search, Pagination, Filter, Sort, and Limit Features

Thematisch verwandte Begriffe: Nextjs, Management, Implement, Search · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...