Skip to content

B. Integrasi List book

1. Integrasi list Buku

Pada materi ini kita akan integrasikan api dari backend untuk digunakan pada frontend menggunakan NextJS. Buatlah folder book di dalam folder app dan buatlah file page.tsx seperti gambar di bawah ini.

Alt text

Membuat Tabel Book

Pada app/book/page.tsx kita membuat user interface dalam bentuk tabel untuk menampilkan buku yang akan didapatkan melalui api list book.

app/book/page.tsx
import { Pagination } from "../../components/Pagination";
import { Table, Th, Thead, Tr, Tbody, Td } from "../../components/Table";

const Book = () => {
  return (
    <>
      <section className="container px-4 mx-auto">
        <Table>
          <Thead>
            <Tr>
              <Th scope="col">
                <div className="flex items-center gap-x-3">
                  <input
                    type="checkbox"
                    className="text-blue-500 border-gray-300 rounded dark:bg-gray-900 dark:ring-offset-gray-900 dark:border-gray-700"
                  />
                  <button className="flex items-center gap-x-2">
                    <span>Invoice</span>
                    <svg
                      className="h-3"
                      viewBox="0 0 10 11"
                      fill="none"
                      xmlns="http://www.w3.org/2000/svg"
                    >
                      <path
                        d="M2.13347 0.0999756H2.98516L5.01902 4.79058H3.86226L3.45549 3.79907H1.63772L1.24366 4.79058H0.0996094L2.13347 0.0999756ZM2.54025 1.46012L1.96822 2.92196H3.11227L2.54025 1.46012Z"
                        fill="currentColor"
                        stroke="currentColor"
                        strokeWidth="0.1"
                      />
                      <path
                        d="M0.722656 9.60832L3.09974 6.78633H0.811638V5.87109H4.35819V6.78633L2.01925 9.60832H4.43446V10.5617H0.722656V9.60832Z"
                        fill="currentColor"
                        stroke="currentColor"
                        strokeWidth="0.1"
                      />
                      <path
                        d="M8.45558 7.25664V7.40664H8.60558H9.66065C9.72481 7.40664 9.74667 7.42274 9.75141 7.42691C9.75148 7.42808 9.75146 7.42993 9.75116 7.43262C9.75001 7.44265 9.74458 7.46304 9.72525 7.49314C9.72522 7.4932 9.72518 7.49326 9.72514 7.49332L7.86959 10.3529L7.86924 10.3534C7.83227 10.4109 7.79863 10.418 7.78568 10.418C7.77272 10.418 7.73908 10.4109 7.70211 10.3534L7.70177 10.3529L5.84621 7.49332C5.84617 7.49325 5.84612 7.49318 5.84608 7.49311C5.82677 7.46302 5.82135 7.44264 5.8202 7.43262C5.81989 7.42993 5.81987 7.42808 5.81994 7.42691C5.82469 7.42274 5.84655 7.40664 5.91071 7.40664H6.96578H7.11578V7.25664V0.633865C7.11578 0.42434 7.29014 0.249976 7.49967 0.249976H8.07169C8.28121 0.249976 8.45558 0.42434 8.45558 0.633865V7.25664Z"
                        fill="currentColor"
                        stroke="currentColor"
                        strokeWidth="0.3"
                      />
                    </svg>
                  </button>
                </div>
              </Th>
              <Th scope="col">Date</Th>
              <Th scope="col">Status</Th>
              <Th scope="col">Customer</Th>
              <Th scope="col">Purchase</Th>
              <Th scope="col">
                <span className="sr-only">Actions</span>
              </Th>
            </Tr>
          </Thead>
          <Tbody>
            <Tr>
              <Td>
                <span>ok</span>
              </Td>
              <Td>
                <span>ok</span>
              </Td>
              <Td>
                <span>ok</span>
              </Td>
              <Td>
                <span>ok</span>
              </Td>
              <Td>
                <span>ok</span>
              </Td>
              <Td>
                <span>ok</span>
              </Td>
            </Tr>
          </Tbody>
        </Table>


      </section>
    </>
  );
};

export default Book;

Endpoint List Book

Lihatlah kembali endpoint list book yang sudah kita buat pada materi backend seperti gambar di bawah ini.

Alt text

request endpoint list
http://localhost:5002/book/list
http://localhost:5002/book/list?page=1&pageSize=1&title=up&author=ihsan&from_year=2022&to_year=2023
response endpoint list
{
    "status": "Success",
    "message": "OK",
    "data": [
        {
            "id": 1,
            "title": "NestJS For Backend",
            "author": "Ihsan",
            "year": 2023,
            "created_at": "2023-08-29T06:27:27.000Z",
            "updated_at": "2023-08-29T06:27:27.000Z"
        },
        {
            "id": 2,
            "title": "Server Admin",
            "author": "Raihan",
            "year": 2022,
            "created_at": "2023-08-29T06:27:27.000Z",
            "updated_at": "2023-08-29T06:27:27.000Z"
        },
        {
            "id": 3,
            "title": "NextJs Developer",
            "author": "Ihsan",
            "year": 2023,
            "created_at": "2023-08-29T06:27:27.000Z",
            "updated_at": "2023-08-29T06:27:27.000Z"
        },
        {
            "id": 4,
            "title": "HTML CSS",
            "author": "Nur",
            "year": 2022,
            "created_at": "2023-08-29T06:27:27.000Z",
            "updated_at": "2023-08-29T06:27:27.000Z"
        },
        {
            "id": 5,
            "title": "TypeScript",
            "author": "Ihsan",
            "year": 2023,
            "created_at": "2023-08-29T06:27:27.000Z",
            "updated_at": "2023-08-29T06:27:27.000Z"
        },
        {
            "id": 6,
            "title": "Become Network Engineer",
            "author": "Fathi",
            "year": 2021,
            "created_at": "2023-08-29T06:27:27.000Z",
            "updated_at": "2023-08-29T06:27:27.000Z"
        },
        {
            "id": 7,
            "title": "Database MySQL",
            "author": "Akbar",
            "year": 2023,
            "created_at": "2023-08-29T06:27:27.000Z",
            "updated_at": "2023-08-29T06:27:27.000Z"
        },
        {
            "id": 8,
            "title": "NestJS For Backend",
            "author": "Ihsan",
            "year": 2023,
            "created_at": "2023-08-30T14:16:40.000Z",
            "updated_at": "2023-08-30T14:16:40.000Z"
        },
        {
            "id": 9,
            "title": "Server Admin",
            "author": "Raihan",
            "year": 2022,
            "created_at": "2023-08-30T14:16:40.000Z",
            "updated_at": "2023-08-30T14:16:40.000Z"
        },
        {
            "id": 10,
            "title": "TypeScript",
            "author": "Ihsan",
            "year": 2023,
            "created_at": "2023-08-30T14:16:40.000Z",
            "updated_at": "2023-08-30T14:16:40.000Z"
        }
    ],
    "pagination": {
        "total": 39,
        "page": 1,
        "total_page": 4,
        "pageSize": 10
    }
}

Membuat Interface Book

Pada file ini kita membuat interface yang akan mendefinikan struktur data yang ada pada fitur book ini.

app/book/interface/index.ts
import { BaseResponsePagination } from "@/lib/axiosClient";

interface Book {
  id: number;
  title: string;
  author: string;
  year: number | undefined | string;
  created_at: string;
  updated_at: string;
}

export interface BookListResponse extends BaseResponsePagination {
  data: Book[];
}

Membuat Service Book

book/lib/index.ts
import { useQuery } from "@tanstack/react-query";
import { axiosClient } from "@/lib/axiosClient";
import { BookListResponse } from "../interface";

const useBookModule = () => {
  const getBookList = async (): Promise<BookListResponse> => {
    return axiosClient.get("/book/list").then((res) => res.data);
  };
  const useBookList = () => {
    const { data, isFetching, isLoading } = useQuery(
      ["/book/list"],
      () => getBookList(),
      {
        keepPreviousData: true,

        select: (response) => response,
      }
    );

    return { data, isFetching, isLoading };
  };

  return { useBookList };
};

export default useBookModule;

Memanggil Service List Book

book/page.tsx
"use client";
import Button from "@/components/Button";
import { Pagination } from "../../components/Pagination";
import { Table, Th, Thead, Tr, Tbody, Td } from "../../components/Table";
import useBookModule from "./lib";
import { useRouter } from "next/navigation";

const Book = () => {
  const { useBookList } = useBookModule();
  const { data, isFetching, isError } = useBookList();

  return (
    <>
      <section className="container px-4 mx-auto space-y-5">
        <section className="flex items-center justify-between">
          <Button colorSchema="red" title="Tambah Buku" />
        </section>
        <section>
          <Table
            isFetching={isFetching}
            isEmpty={data?.data?.length === 0}
            isError={isError}
          >
            <Thead>
              <Tr>
                <Th scope="col">
                  <div className="flex items-center gap-x-3">
                    <input
                      type="checkbox"
                      className="text-blue-500 border-gray-300 rounded dark:bg-gray-900 dark:ring-offset-gray-900 dark:border-gray-700"
                    />
                  </div>
                </Th>
                <Th scope="col">Title</Th>
                <Th scope="col">Author</Th>
                <Th scope="col">Year</Th>
                <Th scope="col">Created At</Th>
                <Th scope="col">Updated At</Th>
              </Tr>
            </Thead>
            <Tbody>
              {data &&
                data.data.map((item, index) => (
                  <Tr key={index}>
                    <Td>
                      <input
                        type="checkbox"
                        className="text-blue-500 border-gray-300 rounded dark:bg-gray-900 dark:ring-offset-gray-900 dark:border-gray-700"
                      />
                    </Td>
                    <Td>
                      <span>{item.title}</span>
                    </Td>
                    <Td>
                      <span>{item.author}</span>
                    </Td>
                    <Td>
                      <span>{item.year}</span>
                    </Td>
                    <Td>
                      <span>{item.created_at}</span>
                    </Td>
                    <Td>
                      <span>{item.updated_at}</span>
                    </Td>
                  </Tr>
                ))}
            </Tbody>
          </Table>
        </section>


      </section>
    </>
  );
};

export default Book;

Jalankan pada Bowser

Alt text

2. Implementasi Filter

book/interface.ts
import {
  BaseResponsePagination,
} from "@/lib/axiosClient";

interface Book {
  id: number;
  title: string;
  author: string;
  year: number | undefined | string;
  created_at: string;
  updated_at: string;
}

export interface BookListResponse extends BaseResponsePagination {
  data: Book[];
}

export interface BookListFilter extends Partial<Book> {
  from_year?: string;
  to_year?: string;
  page : number ,
  pageSize : number
}
book/lib/index.ts
import { useQuery } from "@tanstack/react-query";
import { axiosClient } from "@/lib/axiosClient";
import { BookListFilter, BookListResponse } from "../interface";
import { ChangeEvent, useState } from "react";

const useBookModule = () => {
  const defaultParams = {
    title: "",
    author: "",
    from_year: "",
    to_year: "",
    page: 1,
    pageSize: 10,
  };
  const getBookList = async (
    params: BookListFilter
  ): Promise<BookListResponse> => {
    return axiosClient.get("/book/list", { params }).then((res) => res.data);
  };
  const useBookList = () => {
    let [params, setParams] = useState<BookListFilter>(defaultParams);
    let [filterParams, setFilterParams] =
      useState<BookListFilter>(defaultParams);
    const handleFilter = () => {
      setFilterParams({ ...params });
    };
    const handleClear = () => {
      setFilterParams(defaultParams);
       setParams(defaultParams);
    };

    const handlePageSize = (e: ChangeEvent<any>) => {
      setParams((params) => ({ ...params, pageSize: e.target.value }));
      setFilterParams((params) => ({ ...params, pageSize: e.target.value }));
    };
    const handlePage = (page: number) => {
      setParams((params) => ({ ...params, page: page }));
      setFilterParams((params) => ({ ...params, page: page }));
    };

    const { data, isFetching, isLoading, isError } = useQuery(
      ["/book/list", [filterParams]],
      () => getBookList(filterParams),
      {
        keepPreviousData: true,

        select: (response) => response,
      }
    );

    return {
      data,
      isFetching,
      isLoading,
      isError,
      params,
      setParams,
      handlePageSize,
      handlePage,
      handleFilter,
      handleClear,
    };
  };

  return { useBookList };
};

export default useBookModule;
book/module/filter.ts
import { ChangeEvent, Dispatch, ReactNode, SetStateAction } from "react";
import { BookListFilter } from "@/app/book/interface";
import InputText from "@/components/InputText";
import Label from "@/components/Label";
import Select from "@/components/Select";

type FilterProps = {
  params: BookListFilter;
  setParams: Dispatch<SetStateAction<any>>;
};

const option = [
  {
    value: 2020,
    label: "2020",
  },
  {
    value: 2021,
    label: "2021",
  },
  {
    value: 2022,
    label: "2022",
  },
  {
    value: 2023,
    label: "2023",
  },
];

const Filter: React.FC<FilterProps> = ({ params, setParams }) => {
  const handleChange = (e: ChangeEvent<any>) => {
    setParams((params: BookListFilter) => {
      return {
        ...params,
        [e.target.name]: e.target.value,
      };
    });
  };
  return (
    <section className="space-y-2">
      <section>
        <Label title="Title" htmlFor="title" />
        <InputText
          onChange={handleChange}
          value={params.title}
          name="title"
          id="title"
        />
      </section>
      <section>
        <Label title="Author" htmlFor="author" />
        <InputText
          onChange={handleChange}
          value={params.author}
          name="author"
          id="author"
        />
      </section>
      <section>
        <Label title="Title" htmlFor="from_year" />
        <Select
          onChange={handleChange}
          options={option}
          value={params.from_year}
          name="from_year"
          id="from_year"
        />
      </section>
      <section>
        <Label title="Title" htmlFor="to_year" />
        <Select
          onChange={handleChange}
          options={option}
          value={params.to_year}
          name="to_year"
          id="to_year"
        />
      </section>
    </section>
  );
};

export default Filter;
book/page.tsx
"use client";
import Button from "@/components/Button";
import { Pagination } from "../../components/Pagination";
import { Table, Th, Thead, Tr, Tbody, Td } from "../../components/Table";
import useBookModule from "./lib";
import { Drawer } from "@/components/Drawer";
import Filter from "./module/Filter";
import { useDisclosure } from "@/hook";

const Book = () => {
  const { useBookList } = useBookModule();

  const {
    data,
    isFetching,
    isError,
    params,
    setParams,
    handleFilter,
    handleClear,
    handlePageSize,
    handlePage,
  } = useBookList();

  const { isOpen, onOpen, onClose } = useDisclosure();

  return (
    <>
      <Drawer
        onClose={onClose}
        onClear={handleClear}
        onSubmit={handleFilter}
        title="Filter Buku"
        isOpen={isOpen}
      >
        <Filter params={params} setParams={setParams} />
      </Drawer>
      <section className="w-screen h-screen p-10 overflow-auto ">
        <section className="flex items-center justify-between ">
          <Button
            width="sm"
            onClick={onOpen}
            colorSchema="blue"
            title="Filter"
          />
          <Button width="sm" colorSchema="red" title="tambah" />
        </section>

        <section className="h-full w-full mt-5 ">
          <Table
            isFetching={isFetching}
            isEmpty={data?.data?.length === 0}
            isError={isError}
          >
            <Thead>
              <Tr>
                <Th scope="col">
                  <div className="flex items-center gap-x-3">
                    <input
                      type="checkbox"
                      className="text-blue-500 border-gray-300 rounded dark:bg-gray-900 dark:ring-offset-gray-900 dark:border-gray-700"
                    />
                  </div>
                </Th>
                <Th scope="col">No</Th>
                <Th scope="col">Title</Th>
                <Th scope="col">Author</Th>
                <Th scope="col">Year</Th>
                <Th scope="col">Created At</Th>
                <Th scope="col">Updated At</Th>
              </Tr>
            </Thead>
            <Tbody>
              {data &&
                data.data.map((item, index) => (
                  <Tr key={index}>
                    <Td>
                      <input
                        type="checkbox"
                        className="text-blue-500 border-gray-300 rounded dark:bg-gray-900 dark:ring-offset-gray-900 dark:border-gray-700"
                      />
                    </Td>
                    <Td>{(params.page - 1) * params.pageSize + index + 1}</Td>
                    <Td>
                      <span>{item.title}</span>
                    </Td>
                    <Td>
                      <span>{item.author}</span>
                    </Td>
                    <Td>
                      <span>{item.year}</span>
                    </Td>
                    <Td>
                      <span>{item.created_at}</span>
                    </Td>
                    <Td>
                      <span>{item.updated_at}</span>
                    </Td>
                  </Tr>
                ))}
            </Tbody>
          </Table>

          <Pagination
            page={params.page}
            pageSize={params.pageSize}
            handlePageSize={handlePageSize}
            handlePage={handlePage}
            pagination={data?.pagination}
          />
        </section>
      </section>
    </>
  );
};

export default Book;

Alt text

Alt text

Merapihkan koding useBookList

hook/usePagination.ts
import { ChangeEvent, useState } from "react";

interface PaginationParams {
  page: number;
  pageSize: number;

}

export const usePagination = <T extends PaginationParams>(defaultParams: T) => {
  let [params, setParams] = useState<T>(defaultParams);
  let [filterParams, setFilterParams] = useState<T>(defaultParams);

  const handleFilter = () => {
    setFilterParams({ ...params });
  };

  const handleClear = () => {
    setFilterParams(defaultParams);
    setParams(defaultParams);
  };

  const handlePageSize = (e: ChangeEvent<any>) => {
    setParams((params) => ({ ...params, pageSize: e.target.value }));
    setFilterParams((params) => ({ ...params, pageSize: e.target.value }));
  };

  const handlePage = (page: number) => {
    setParams((params) => ({ ...params, page: page }));
    setFilterParams((params) => ({ ...params, page: page }));
  };

  return {
    params,
    setParams,
    handleFilter,
    handleClear,
    handlePageSize,
    handlePage,
    filterParams,
  };
};
book/lib/index.ts
import { useQuery } from "@tanstack/react-query";
import { axiosClient } from "@/lib/axiosClient";
import { BookListFilter, BookListResponse } from "../interface";
import { usePagination } from "@/hook/usePagination";

const useBookModule = () => {
  const defaultParams : BookListFilter = {
    title: "",
    author: "",
    from_year: "",
    to_year: "",
    page: 1,
    pageSize: 10,
  };
  const getBookList = async (
    params: BookListFilter
  ): Promise<BookListResponse> => {
    return axiosClient.get("/book/list", { params }).then((res) => res.data);
  };
  const useBookList = () => {
    const{
      params,
      setParams,
      handleFilter,
      handleClear,
      handlePageSize,
      handlePage,
      filterParams
    } = usePagination(defaultParams)

    const { data, isFetching, isLoading, isError } = useQuery(
      ["/book/list", [filterParams]],
      () => getBookList(filterParams),
      {
        keepPreviousData: true,

        select: (response) => response,
      }
    );

    return {
      data,
      isFetching,
      isLoading,
      isError,
      params,
      setParams,
      handlePageSize,
      handlePage,
      handleFilter,
      handleClear,
    };
  };

  return { useBookList };
};

export default useBookModule;