728x90

Next.js 컴포넌트화


현재 Front 쪽 코드는 아래와 같습니다.

import {FormEventHandler} from "react";
import {redirect} from "next/navigation";


export default async function Home() {


    async function getData(){
        "use server";
        const url = new URL("http://localhost:4882")
        Object.keys(searchParams).map((param:string)=>{
            url.searchParams.append(param,searchParams[param] as string)
        });
        let resp = await fetch(url);
        let result = await resp.json()
        return result.data
    }

    const items = await getData();
    return (
        <main className="flex min-h-screen flex-col items-center justify-between p-24">
            <form className={"w-full overflow-hidden"}>
                <div className="pt-2 relative text-gray-600 w-px245 w-max">
                    <input
                        className="border-2 border-gray-300 bg-white h-10 px-5 pr-16 rounded-lg text-sm focus:outline-none relative"
                        type="search" name="srchTxt" placeholder="Search"/>
                    <button type="submit" className="absolute right-0 top-0 mt-5 mr-4">
                        <svg className="text-gray-600 h-4 w-4 fill-current" xmlns="http://www.w3.org/2000/svg"
                             xmlnsXlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px"
                             viewBox="0 0 56.966 56.966" style={{background: "new 0 0 56.966 56.966"}}
                             xmlSpace="preserve"
                             width="512px" height="512px">
                            <path
                                d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23  s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92  c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17  s-17-7.626-17-17S14.61,6,23.984,6z"/>
                        </svg>
                    </button>
                </div>

                <div className="flex flex-col">
                    <div className="overflow-x-auto sm:-mx-6 lg:-mx-8">
                        <div className="inline-block min-w-full py-2 sm:px-6 lg:px-8">
                            <div className="overflow-hidden">
                                <table
                                    className="min-w-full text-left text-sm font-light text-surface dark:text-white">
                                    <thead
                                        className="border-b border-neutral-200 font-medium dark:border-white/10">
                                    <tr>
                                        <th scope="col" className="px-6 py-4">#</th>
                                        <th scope="col" className="px-6 py-4">First</th>
                                        <th scope="col" className="px-6 py-4">Last</th>
                                        <th scope="col" className="px-6 py-4">Handle</th>
                                    </tr>
                                    </thead>
                                    <tbody>
                                    {items?.map((item:{[key:string]:string})=> {
                                        return <tr className="border-b border-neutral-200 dark:border-white/10" key={Math.random()}>
                                            <td className="whitespace-nowrap px-6 py-4 font-medium">{item["번호"]}</td>
                                            <td className="whitespace-nowrap px-6 py-4">{item[Object.keys(item)[1]]}</td>
                                            <td className="whitespace-nowrap px-6 py-4">{item[Object.keys(item)[2]]}</td>
                                            <td className="whitespace-nowrap px-6 py-4">{item[Object.keys(item)[3]]}</td>
                                        </tr>
                                    })}
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                </div>

                <nav aria-label="Page navigation example">
                    <ul className="list-style-none flex">
                        <li>
                            <a
                                className="relative block rounded bg-transparent px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 focus:bg-neutral-100 focus:text-primary-700 focus:outline-none focus:ring-0 active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500"
                                href="#"
                            >Previous</a
                            >
                        </li>
                        <li>
                            <a
                                className="relative block rounded bg-transparent px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 focus:bg-neutral-100 focus:text-primary-700 focus:outline-none active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500"
                                href="#"
                            >1</a
                            >
                        </li>
                        <li aria-current="page">
                            <a
                                className="relative block rounded bg-transparent px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 focus:bg-neutral-100 focus:text-primary-700 focus:outline-none active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500"
                                href="#"
                            >2</a
                            >
                        </li>
                        <li>
                            <a
                                className="relative block rounded bg-transparent px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 focus:bg-neutral-100 focus:text-primary-700 focus:outline-none active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500"
                                href="#"
                            >3</a
                            >
                        </li>
                        <li>
                            <a
                                className="relative block rounded bg-transparent px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 focus:bg-neutral-100 focus:text-primary-700 focus:outline-none active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500"
                                href="#"
                            >Next</a
                            >
                        </li>
                    </ul>
                </nav>
            </form>
        </main>
    );
}

 

response 받은 데이터를 기반으로 컴포넌트들을 그려보면 좋을 거 같습니다.

 

src 디렉토리에 libs 디렉토리를 만들고 그 하위에 components 를 만들어 줍니다.

 

처음으로 만들 컴포넌트는 3개입니다.

 

1. 검색 바

2. 데이터 리스트

3. paging navigation

 

먼저 각 부분 디렉토리를 만들어 주고, 그안에 index.tsx 그리고 컴포넌의 tsx 파일을 만들고 코드를 옮겨줍니다.

 

 

1. 검색 바 - SearchBar


export default function SearchBar({searchText}:{searchText:string}) {

    return <div className="pt-2 relative text-gray-600 w-px245 w-max">
        <input
            className="border-2 border-gray-300 bg-white h-10 px-5 pr-16 rounded-lg text-sm focus:outline-none relative"
            type="search" name="srchTxt" placeholder="Search" defaultValue={searchText || ""}/>
        <button type="submit" className="absolute right-0 top-0 mt-5 mr-4">
            <svg className="text-gray-600 h-4 w-4 fill-current" xmlns="http://www.w3.org/2000/svg"
                 xmlnsXlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px"
                 viewBox="0 0 56.966 56.966" style={{background: "new 0 0 56.966 56.966"}}
                 xmlSpace="preserve"
                 width="512px" height="512px">
                <path
                    d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23  s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92  c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17  s-17-7.626-17-17S14.61,6,23.984,6z"/>
            </svg>
        </button>
    </div>
}

 

 

검색 바의 경우에는 기본 value 값이 필요합니다. 검색 입력 된 텍스트를 기본 value로 넣어 주는데, Next.js의 경우에는 defaultValue에 넣어 줍니다.

 

2. DataList


type item = { [key: string]: string }

type PropsType = {
    items?: item[]
}

export default function DataList({items}:PropsType) {
    return <div className={"grow"}>
        <div className="overflow-x-auto sm:-mx-6 lg:-mx-8">
            <div className="inline-block min-w-full py-2 sm:px-6 lg:px-8 grow-1">
                <div className="overflow-hidden">
                    <table
                        className="min-w-full text-left text-sm font-light text-surface dark:text-white">
                        <thead
                            className="border-b border-neutral-200 font-medium dark:border-white/10">
                        <tr>
                            <th scope="col" className="px-3 sm:px-6 py-3 sm:py-4">#</th>
                            <th scope="col" className="px-3 sm:px-6 py-3 sm:py-4">First</th>
                            <th scope="col" className="px-3 sm:px-6 py-3 sm:py-4">Last</th>
                            <th scope="col" className="px-3 sm:px-6 py-3 sm:py-4">Handle</th>
                        </tr>
                        </thead>
                        <tbody>
                        {items?.map((item: { [key: string]: string }) => {
                            return <tr className="border-b border-neutral-200 dark:border-white/10"
                                       key={Math.random()}>
                                <td className="whitespace-nowrap px-3 sm:px-6 py-3 sm:py-4 font-medium">{item["번호"]}</td>
                                <td className="whitespace-nowrap px-3 sm:px-6 py-3 sm:py-4">{item[Object.keys(item)[1]]}</td>
                                <td className="whitespace-nowrap px-3 sm:px-6 py-3 sm:py-4">{item[Object.keys(item)[2]]}</td>
                                <td className="whitespace-nowrap px-3 sm:px-6 py-3 sm:py-4">{item[Object.keys(item)[3]]}</td>
                            </tr>
                        })}
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
}

 

 

 

3. PagingNav


import {PagingType} from "./utils.d"

export class Paging {

    pagingSize: number = 10;
    curPagingSize: number = 10;
    curFirstPage: number = 1;
    curLastPage: number = 0;
    curPrevPage: number = 0;
    curNextPage: number = 0;
    lastPage: number = 0;
    curPrevPageOk: boolean = false;
    curNextPageOk: boolean = false;

    constructor(paging: PagingType) {
        this.pagingSize = paging?.pagingSize || this.pagingSize;
        this.curFirstPage = this.getCurFirstPage(paging.page);
        this.curLastPage = this.getCurLastPage(paging.page, paging.totalPage);
        this.curPagingSize = this.curLastPage - this.curFirstPage + 1;
        this.curPrevPage = paging.page - 1;
        this.curNextPage = paging.page + 1;
        this.curPrevPageOk = 0 <= this.curPrevPage;
        this.curNextPageOk =this.curNextPage < paging.totalPage;
        this.lastPage = paging.totalPage;
    }

    getCurLastPage(page: number, totalPage: number): number {
        let curLp: number = this.getCurFirstPage(page) + (this.pagingSize - 1);
        return curLp >= totalPage ? totalPage : curLp;
    }

    getCurFirstPage(page: number): number {
        return Math.floor((page - 1) / this.pagingSize) * this.pagingSize + 1;
    }
}
import {PagingType} from "@/libs/utils/utils.d";
import {Paging} from "@/libs/utils/utils";

type PropsType = {
    paging:PagingType
}

export default function PagingNav({paging}:PropsType) {

    const page = new Paging(paging);
    return <nav aria-label="Page navigation example">
        <ul className="list-style-none flex">
            {page.curPrevPageOk && <>
                <li>
                    <button
                        className="relative block rounded bg-transparent px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 focus:bg-neutral-100 focus:text-primary-700 focus:outline-none focus:ring-0 active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500"
                        type={"submit"} value={1} name={"page"}
                    >First
                    </button
                    >
                </li>
                <li>
                    <button
                        className="relative block rounded bg-transparent px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 focus:bg-neutral-100 focus:text-primary-700 focus:outline-none focus:ring-0 active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500"
                        type={"submit"} value={page.curPrevPage} name={"page"}
                    >Previous
                    </button
                    >
                </li>

            </>}
            {Array(page.curPagingSize).fill(0).map((_, i) => {
                return <li>
                    <button
                        className={`relative block rounded px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 hover:text-black focus:bg-neutral-100 focus:text-primary-700 focus:outline-none active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500 ${paging.page === page.curFirstPage + i && "bg-black text-white"}`}
                        type={"submit"} value={page.curFirstPage + i} name={"page"}
                    >{page.curFirstPage + i}</button>
                </li>
            })}
            {page.curNextPageOk && <>
                <li>
                    <button
                        className="relative block rounded bg-transparent px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 focus:bg-neutral-100 focus:text-primary-700 focus:outline-none active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500"
                        type={"submit"} value={page.curNextPage} name={"page"}
                    >Next
                    </button>
                </li>
                <li>
                    <button
                        className="relative block rounded bg-transparent px-3 py-1.5 text-sm text-surface transition duration-300 hover:bg-neutral-100 focus:bg-neutral-100 focus:text-primary-700 focus:outline-none active:bg-neutral-100 active:text-primary-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:focus:text-primary-500 dark:active:bg-neutral-700 dark:active:text-primary-500"
                        type={"submit"} value={page.lastPage} name={"page"}
                    >Last
                    </button>
                </li>
            </>}
        </ul>
    </nav>
}

 

 

4.Page


import {FormEventHandler} from "react";
import {redirect} from "next/navigation";
import {Paging} from "../libs/utils/utils";
import SearchBar from "@/libs/components/SearchBar/SearchBar";
import DataList from "@/libs/components/DataList/DataList";
import PagingNav from "@/libs/components/PagingNav/PagingNav";

type searchParamsType = { [key: string]: string | null }
export default async function Home({searchParams}: { searchParams: searchParamsType }) {

    async function getData(searchParam?: searchParamsType) {
        "use server";
        const url = new URL("http://localhost:4882")
        Object.keys(searchParams).map((param: string) => {
            url.searchParams.append(param, searchParams[param] as string)
        });
        let resp = await fetch(url, {cache:"no-store"});
        // let resp = await fetch(url, {next:{revalidate:60*60}});
        let result = await resp.json()
        return result
    }

    async function go(formData: FormData = new FormData()) {
        "use server";
        let curPage:string | null = searchParams["page"]
        let curSrchTxt:string | null = searchParams["srchTxt"]
        let page:FormDataEntryValue | null = formData.get('page')
        const url = new URL("http://localhost:3000")
        url.searchParams.append("srchTxt", formData.get('srchTxt') as string);
        if(curPage && !page){
            url.searchParams.append("page", curPage as string);
        }else if(page){
            url.searchParams.append("page", page as string);
        }

        if(curSrchTxt===formData.get('srchTxt')as string && curPage===page){
            return false;
        }

        redirect(url.toString());

    }

    const result = await getData(searchParams);
    const {items, paging} = result;

    return (
        <main className="flex min-h-screen flex-col items-center justify-between p-6 sm:p-24 h-screen">
            <form action={go} className={"w-full flex flex-col grow"}>

                <SearchBar searchText={searchParams["srchTxt"] as string}/>

                <DataList items={items}/>
                <PagingNav paging={paging} />
            </form>
        </main>
    );
}

 

728x90

+ Recent posts