본문으로 바로가기

이번 목표

  • Axios 라이브러리를 이용한 서버와의 통신
  • useEffect() 를 활용한 비동기 처리와 상태 변경
  • 커스텀 훅을 이용한 공통 코드 재사용하기
  • 컴포넌트에서 모달창을 이용해서 결과 보여주기

 

4.1  Ajax 통신 처리

  • Axios 라이브러리를 추가해 준다.
  • npm install axios
  • Axios 의 경우 기본적으로 데이터 형식이 JSON 을 사용한다.

todoApi.js

import axios from "axios";

export const API_SERVER_HOST = 'http://localhost:8080';
const prefix = `${API_SERVER_HOST}/api/todo`;

export const getOne = async(tno) =>{
    const res = await axios.get(`${prefix}/${tno}`);

    return res.data;
}

export const getList = async (pageParam) =>{
    const {page, size} = pageParam;
    const res = await axios.get(`${prefix}/list`,{params: {page:page, size:size}});

    return res.data;
}

 

 

 

4.2 useEffect()

  • 비동기 처리는 함수를 호출하고 호출 결과를 기다려서 다음 처리가 진행되는 방식이 아니고 나중에 결과를 처리하는 방식임
  • 리액트의 경우는 컴포넌트에서 비동기 방식으로 호출했다면 호출 결과를 처리한 후에 상태를 변경해서 처리
    • 문제점 : 컴포넌트의 상태가 변경되었기 때문에 컴포넌트는 다시 렌더링을 호출
    • 다시 랜더링이 되면서 비동기 호출을 하게 되고 잠시 후 다시 상태가 갱신되는 악순한이 계속 반복됨!!
  • 리액트에서는 useEffect() 로 컴포넌트 내에 특정한 상황을 만족하는 경우에만 특정한 동작을 수행하는 방법을 제공함
  • ex) 컴포넌트 실행 과정에서 한 번만 실행해야 하는 비동기 철
  • ex) 컴포넌트의 여러 상태 중에서 특정한 상태만 변경되었을 경우에 비동기 처리

 

조회를 위한 컴포넌트

  • pages 폴더는 주로 URL 처리를 위한 컴포넌트이고, 실제 작업은 components 폴더를 이용해서 구성
  • useEffect() 를 사용해서 번호가 변경 되었을 때만 Axios를 이용하는 getOne() 을 호출하도록 구성됨

ReadComponent.js

import React, { useEffect, useState } from 'react';
import { getOne } from '../../api/todoApi';

const initState = {
    tno:0,
    title:'',
    writer:'',
    dueDate:null,
    complete:false
}

const ReadComponent = ({tno}) => {
    const [todo, setTodo] = useState(initState);

    useEffect(()=>{
        getOne(tno).then(data =>{
            console.log(data);
            setTodo(data);
        })
    }, [tno])



    return (
        <div>
            
        </div>
    );
};

export default ReadComponent;

 

ReadPage.js

import React, { useCallback } from 'react';

import { createSearchParams, useNavigate, useParams, useSearchParams } from 'react-router-dom';
import ReadComponent from '../../components/todo/ReadComponent';

const ReadPage = () => {

    const { tno } = useParams();
    const navigate = useNavigate();
    const [queryParams] = useSearchParams();
    const page = queryParams.get("page") ? parseInt(queryParams.get("page")) : 1;
    const size = queryParams.get("size") ? parseInt(queryParams.get("size")) : 10;
    const queryStr = createSearchParams({ page, size }).toString();



    return (

        <div className='font-extrabold w-full bg-white mt-6'>
            Todo Read Page Component {tno}
            <div className='text-2xl'>
                Todo Read Page Component {tno}
            </div>

            <ReadComponent tno={tno}></ReadComponent>
        </div>
    );
};


export default ReadPage;

 

 

<StrictMode> 설정

  • 아래 그림을 보면 Axios 호출이 두번 된것을 확인할 수 있다 (33 두번 나옴)
  • 비동기 호출은 두번 호출되는 경우가 발생한다.
  • index.js 에서 strictMode 를 빼주면 해결 !!

 

변경전

 <React.StrictMode>
    <App />
  </React.StrictMode>

 

변경 후

root.render(
    <App />
);

 

 

ReadComponent.js

import React, { useEffect, useState } from 'react';
import { getOne } from '../../api/todoApi';

const initState = {
    tno:0,
    title:'',
    writer:'',
    dueDate:null,
    complete:false
}

const ReadComponent = ({tno}) => {
    const [todo, setTodo] = useState(initState);

    useEffect(()=>{
        getOne(tno).then(data =>{
            console.log(data);
            setTodo(data);
        })
    }, [tno])



    return (
        <div className='torder-2 border-sky-200 mt-10 m-2 p-4'>
            {makeDiv('Tno', todo.tno)}
            {makeDiv('Writer', todo.writer)}
            {makeDiv('Title', todo.title)}
            {makeDiv('Due Date', todo.dueDate)}
            {makeDiv('Complete', todo.complete ? 'Completed' : 'Not Yet')}
        </div>
    );
};

const makeDiv = (title, value)=>{
    return (
        <div className='flex justify-center'>
            <div className='relative mb-4 flex w-full flex-warp items-stretch'>
                <div className='w-1/5 p-6 text-right font-bold'>
                    {title}
                </div>
                <div className='w-4/5 p-6 rounded-r border border-solid shadow-md'>
                    {value}
                </div>
            </div>
        </div>
    );
}

export default ReadComponent;

 

화면

 

 

4.3 네비게이션 관련 커스텀 훅

  • 컴포넌트들 내부에서 만들어지는 공통적인 코드의 경우 커스텀 훅을 이용해서 작성함
  • 커스텀 훅은 반드시 use 로 시작해야됨!!!

 

목록 페이지로 이동

 

useCustomMove.js

import { createSearchParams, useNavigate, useSearchParams } from 'react-router-dom';

const getNum = (param, defaultValue)=>{
    if(!param){
        return defaultValue;
    }

    return parseInt(param);
}

const useCustomMove = () => {
    const navigate = useNavigate();
    const[queryParams] = useSearchParams();
    const page = getNum(queryParams.get('page'),1);
    const size = getNum(queryParams.get('size'),10);
    const queryDefault = createSearchParams({page, size}).toString();

    const moveToList = (pageParam) =>{
        let queryStr = "";
        
        if(pageParam){
            const pageNum = getNum(pageParam.page,1);
            const sizeNum = getNum(pageParam.size, 10);
            
            queryStr = createSearchParams({page: pageNum, size: sizeNum}).toString();
        
        }else{
            queryStr = queryDefault;
        }

        navigate({pathname: `../list`, search:queryStr});
    }

    return {moveToList, page, size}
};

export default useCustomMove;

 

ReadComponent.js

import React, { useEffect, useState } from 'react';
import { getOne } from '../../api/todoApi';
import useCustomMove from '../../hooks/useCustomMove';

const initState = {
    tno:0,
    title:'',
    writer:'',
    dueDate:null,
    complete:false
}

const ReadComponent = ({tno}) => {
    const [todo, setTodo] = useState(initState);
    const {moveToList} = useCustomMove();

    useEffect(()=>{
        getOne(tno).then(data =>{
            console.log(data);
            setTodo(data);
        })
    }, [tno])



    return (
        <div className='border-2 border-sky-200 mt-10 m-2 p-4'>
            {makeDiv('Tno', todo.tno)}
            {makeDiv('Writer', todo.writer)}
            {makeDiv('Title', todo.title)}
            {makeDiv('Due Date', todo.dueDate)}
            {makeDiv('Complete', todo.complete ? 'Completed' : 'Not Yet')}

            <div className='flex justofy-end p-4'>
                <button type='button'
                    className='rounded p-4 text-xl w-32 text-white bg-blue-500'
                    onClick={()=>moveToList()}> 
                    List
                </button>
            </div>
        </div>
    );
};

const makeDiv = (title, value)=>{
    return (
        <div className='flex justify-center'>
            <div className='relative mb-4 flex w-full flex-warp items-stretch'>
                <div className='w-1/5 p-6 text-right font-bold'>
                    {title}
                </div>
                <div className='w-4/5 p-6 rounded-r border border-solid shadow-md'>
                    {value}
                </div>
            </div>
        </div>
    );
}

export default ReadComponent;

 

 

수정/삭제 페이지로 이동

 

useCustomMove.js

 const moveToModify=(num)=>{
        console.log(queryDefault);

        navigate({pathname:`../modify/${num}`, search: queryDefault})
    }

 

ReadComponent.js

<button type='button'
                    className='rounded p-4 m-2 text-xl w-32 text-white bg-blue-500'
                    onClick={()=>moveToModify(tno)}>
                    Modify        
                </button>

 

 

4.4 목록 데이터 처리

 

목록 데이터 가져오기

 

ListComponent.js

import React, { useEffect, useState } from 'react';
import useCustomMove from '../../hooks/useCustomMove';
import { getList } from '../../api/todoApi';

const initState ={
    dtoList:[],
    pageNumList:[],
    pageRequestDTO:null,
    prev:false,
    next:false,
    totalCount:0,
    prevPage:0,
    nextPage:0,
    totalPage:0,
    current:0
}

const ListComponent = () => {
    const {page, size} = useCustomMove();
    const [serverData, setServerData] = useState(initState);

    useEffect(()=>{
        getList({page,size}).then(data=>{
            console.log(data);
            setServerData(data);
        })
    },[page,size])


    return (
        <div>
            Todo List Component
        </div>
    );
};

export default ListComponent;

 

 

ListComponent.js

import React, { useEffect, useState } from 'react';
import useCustomMove from '../../hooks/useCustomMove';
import { getList } from '../../api/todoApi';

const initState ={
    dtoList:[],
    pageNumList:[],
    pageRequestDTO:null,
    prev:false,
    next:false,
    totalCount:0,
    prevPage:0,
    nextPage:0,
    totalPage:0,
    current:0
}

const ListComponent = () => {
    const {page, size} = useCustomMove();
    const [serverData, setServerData] = useState(initState);

    useEffect(()=>{
        getList({page,size}).then(data=>{
            console.log(data);
            setServerData(data);
        })
    },[page,size])


    return (
        <div className='border-2 border-blue-100 mt-10 mr-2 ml-2'>
            <div className='flex flex-wrap mx-auto justify-center p-6'>
                {serverData.dtoList.map(todo => {
                    return(
                        <div key={todo.tno} className='w-full min-w-[400px] p-2 m-2 rounded shadow-md'>
                            <div className='flex'>
                                <div className='font-extrabold text-2xl p-2 w-1/12'>
                                    {todo.tno}
                                </div>
                                <div className='font-extrabold text-1xl m-1 p-2 w-8/12'>
                                    {todo.title}
                                </div>
                                <div className='font-medium text-1xl m-1 p-2 w-2/10'>
                                    {todo.dueDate}
                                </div>
                            </div>    
                        </div>
                    )
                })}
            </div>
        </div>
    );
};

export default ListComponent;

 

 

페이징 처리

 

PageComponent.js

const PageComponent = ({serverData, movePage})=>{
    return(
        <div className="m-6 flex justify-center">
            {serverData.prev ? 
                <div className="m-2 p-2 w-16 text-center font-bold text-blue-400"
                    onClick={()=>movePage({page:serverData.prevPage})}>
                        Prev
                </div> : <></>}

            {serverData.pageNumList.map(pageNum =>
                <div key={pageNum}
                    className={`m-2 p-2 w-12 text-center rounded shadow-md text-white${serverData.current === pageNum ? 
                    'bg-gray-500' : 'bg-blue-400'}`}
                    onClick={()=>movePage({page:pageNum})}>
                    {pageNum}        
                </div>)}

            {serverData.next ? 
                <div className="m-2 p-2 w-16 text-center font-bold text-blue=400"
                    onClick={()=>movePage({page:serverData.nextPage})}>
                        Next
                </div> :<></> }       
        </div>
    )
}

export default PageComponent;

 

ListComponent.js

import React, { useEffect, useState } from 'react';
import useCustomMove from '../../hooks/useCustomMove';
import { getList } from '../../api/todoApi';
import PageComponent from '../common/PageComponent';

const initState ={
    dtoList:[],
    pageNumList:[],
    pageRequestDTO:null,
    prev:false,
    next:false,
    totalCount:0,
    prevPage:0,
    nextPage:0,
    totalPage:0,
    current:0
}

const ListComponent = () => {
    const {page, size, moveToList} = useCustomMove();
    const [serverData, setServerData] = useState(initState);

    useEffect(()=>{
        getList({page,size}).then(data=>{
            console.log(data);
            setServerData(data);
        })
    },[page,size])


    return (
        <div className='border-2 border-blue-100 mt-10 mr-2 ml-2'>
            <div className='flex flex-wrap mx-auto justify-center p-6'>
                {serverData.dtoList.map(todo => {
                    return(
                        <div key={todo.tno} className='w-full min-w-[400px] p-2 m-2 rounded shadow-md'>
                            <div className='flex'>
                                <div className='font-extrabold text-2xl p-2 w-1/12'>
                                    {todo.tno}
                                </div>
                                <div className='font-extrabold text-1xl m-1 p-2 w-8/12'>
                                    {todo.title}
                                </div>
                                <div className='font-medium text-1xl m-1 p-2 w-2/10'>
                                    {todo.dueDate}
                                </div>
                            </div>    
                        </div>
                    )
                })}
            </div>

            <PageComponent serverData={serverData} movePage={moveToList}></PageComponent>
        </div>
    );
};

export default ListComponent;

 

화면

 

동일 페이지 클릭시에도 서버 호출 하고 싶을 경우 처리 방법

  • refresh 설정값을 false로 선언 후 refresh 가 false 인 경우 계속 호출되도록 수정했다.

useCustomMove.js

 const [refresh, setRefresh] = useState(false);
 
 
    const moveToList = (pageParam) =>{

        let queryStr = "";
        
        if(pageParam){
            const pageNum = getNum(pageParam.page,1);
            const sizeNum = getNum(pageParam.size, 10);
            
            queryStr = createSearchParams({page: pageNum, size: sizeNum}).toString();
        
        }else{
            queryStr = queryDefault;
        }
        
        setRefresh(!refresh); // 추가 

        navigate({pathname: `../list`, search:queryStr});
    }

 

ListComponent.js

  useEffect(()=>{
        getList({page,size}).then(data=>{
            console.log(data);
            setServerData(data);
        })
    },[page,size,refresh])

 

 

조회 페이지 이동

  • 번호 클릭시 상세 페이지로 이동가능하게 구현

 

useCustomMove.js

    const moveToRead = (num) =>{
        console.log(queryDefault);

        navigate({pathname: `../read/${num}`, search:queryDefault});
    }

 

ListComponent.js

  • onClick 이벤트 추가
<div key={todo.tno} className='w-full min-w-[400px] p-2 m-2 rounded shadow-md'
                            onClick={()=>moveToRead(todo.tno)}>
                            <div className='flex'>
                                <div className='font-extrabold text-2xl p-2 w-1/12'>
                                    {todo.tno}
                                </div>
                                <div className='font-extrabold text-1xl m-1 p-2 w-8/12'>
                                    {todo.title}
                                </div>
                                <div className='font-medium text-1xl m-1 p-2 w-2/10'>
                                    {todo.dueDate}
                                </div>
                            </div>    
                        </div>

 

 

4.5 등록 컴포넌트와 모달창 처리

AddComponent.js

import React, { useState } from 'react';

const initState = {
    title: '',
    writer: '',
    dueDate: ''
}

const AddComponent = () => {
    const[todo, setTodo] = useState({...initState});

    const handleChangeTodo = (e) =>{
        todo[e.target.name] = e.target.value;

        setTodo({...todo});
    }

    const handleClickAdd = () =>{
        console.log(todo);
    }


    return (
        <div className='border-2 border-sky-200 mt-10 m-2 p-4'>
            <div className='flex justify-center'>
                <div className='relative mb-4 flex w-full flex-wrap items-stretch'>
                    <div className='w-1/5 p-6 text-right font-bold'>
                        TITLE
                    </div>
                    <input className='w-4/5 p-6 rounded-r border border-solid border-neutral-500 shadow-md'
                        name='title'
                        type={'text'}
                        value={todo.title}
                        onChange={handleChangeTodo}>  
                    </input>
                </div>
            </div>

            <div className='flex justify-center'>
                <div className='relative mb-4 flex w-full flex-wrap items-stretch'>
                    <div className='w-1/5 p-6 text-right font-bold'>
                        WRITER
                    </div>
                    <input className='w-4/5 p-6 rounded-r border border-solid border-neutral-500 shadow-md'
                        name='writer'
                        type={'text'}
                        value={todo.writer}
                        onChange={handleChangeTodo}>
                    </input>
                </div>
            </div>

            <div className='flex justify-center'>
                <div className='relative mb-4 flex w-full flex-wrap items-stretch'>
                    <div className='w-1/5 p-6 text-right font-bold'>
                        DUEDATE
                    </div>
                    <input className='w-4/5 p-6 rounded-r border border-solid border-neutral-500 shadow-md'
                        name='dueDate'
                        type={'date'}
                        value={todo.dueDate}
                        onChange={handleChangeTodo}>
                    </input>
                </div>
            </div>

            <div className='flex justify-center'>
                <div className='relative mb-4 flex p-4 flex-wrap items-stretch'>
                    <button type='button'
                        className='rounded p-4 w-36 bg-blue-500 text-xl text-white'
                        onClick={handleClickAdd}>
                        ADD
                    </button>
                </div>
            </div>
        </div>
    );
};

export default AddComponent;

 

AddPage.js

import React from 'react';
import AddComponent from '../../components/todo/AddComponent';

const AddPage = () => {
    return (
        <div className='p-4 w-full bg-white'>
            <div className='text-3xl font-extrabold'>
                Todo Add Page
            </div>

            <AddComponent/>
        </div>   
    );
};

export default AddPage;

 

화면

 

 

서버 호출 확인

 

todoApi.js

export const postAdd = async (todoObj) =>{
    const res = await axios.post(`${prefix}/`, todoObj);

    return res.data;
}

 

AddComponent.js

  const handleClickAdd = () =>{
        postAdd(todo).then(result =>{
            console.log(result);
            setTodo({...initState});
        }).catch(e=>{
            console.error(e);
        })
    }

 

결과 확인

 

 

 

모달 컴포넌트 구현

  • 서버와의 통신이 끝나고 나면 result 상태를 변경해서 보이게 한다!! 
  • 모달 영역을 클릭하거나 close modal 버튼을 클릭시 result 상태를 다시 null로 변경시켜 닫도록 구현되었다.

 

ResultModal.js

const ResultModal = ({title, content, callbackFn}) =>{
    return(
        <div className={`fixed top-0 left-0 z-[1055] flex h-full w-full justify-center bg-black bg-opacity-20`}
            onClick={()=>{
                if(callbackFn){
                    callbackFn();
                }
            }}>

            <div className="absolute bg-white shadow dark:bg-gray-700 opacity-100 w-1/4 rounded
                mt-10 mb-10 px-6 min-w-[600px]">
                <div className="justify-center bg-warning-400 mt-6 mb-6 text-2xl border-b-4 border-gray-500">
                    {title}
                </div>
                <div className="text-4xl border-orange-400 border-b-4 pt-4 pb-4">
                    {content}
                </div>
                <div className="justify-end flex">
                    <button className="rounded bg-blue-500 mt-4 mb-4 px-6 pt-4 pb-4 text-lg text-white"
                        onClick={()=>{
                            if(callbackFn){
                                callbackFn();
                            }
                        }}>
                        Close Modal
                    </button>
                </div>
            </div>
        </div>
    )
}

export default ResultModal;

 

AddComponent.js

const closeModal = () =>{
    setResult(null);
}
    
{/*모달 */}
{result ? <ResultModal title={'Add Result'} content={`New ${result} Added`} 
    callbackFn={closeModal}/> : <></>}

 

화면

 

 

페이지 이동

AddComponent.js

const {moveToList} = useCustomMove();

 const closeModal = () =>{
        setResult(null);
        moveToList();
    }

 

 

4.6 수정 / 삭제 처리

  • 삭제 버튼 : 삭제 결과를 모달창으로 보여주고 'todo/list' 로 이동
  • 수정 버튼 : 수정 결과를 모달창으로 보여주고 '/todo/read/번호' 로 이동

ModifyPage.js

import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import ModifyComponent from '../../components/todo/ModifyComponent';

const ModifyPage = () => {
    const {tno} = useParams();

    return (
        <div className='p-4 w-full bg-white'>
            <div className='text-3xl font-extrabold'>
                Todo Modify Page
            </div>

            <ModifyComponent tno={tno}/>
        </div>
    );
};

export default ModifyPage;

 

ModifyComponent.js

import React, { useEffect, useState } from 'react';
import { deleteOne, getOne, putOne } from '../../api/todoApi';
import useCustomMove from '../../hooks/useCustomMove';
import ResultModal from '../common/ResultModal';

const initState ={
    tno:0,
    title:'',
    writer:'',
    dueDate:'null',
    complete:false
}

const ModifyComponent = ({tno, moveList, moveRead}) => {
    const [todo, setTodo] = useState({...initState});
    // 모달 창을 위한 상태
    const [result, setResult] = useState(null);
    // 이동 
    const {moveToList, moveToRead} = useCustomMove();

    useEffect(()=>{
        getOne(tno).then(data => setTodo(data));
    }, [tno])


    const handleChangeTodo = (e) =>{
        todo[e.target.name] = e.target.value;

        setTodo({...todo});
    }

    const handleChangeTodoComplete = (e) =>{
        const value = e.target.value;

        todo.complete = (value === 'Y');
        setTodo({...todo});
    }

    const handleClickModify = () =>{
        putOne(todo).then(data=>{
            console.log(`modify result : ${data}`);

            setResult('Modified');
        })
    }

    const handleClickDelete = () =>{
        deleteOne(tno).then(data =>{
            console.log(`delete result: ` + data);

            setResult("Deleted");
        })
    }

    // 모달 창 닫기
    const closeModal = () =>{
        if(result === 'Deleted'){
            moveToList();
        }else{
            moveToRead(tno);
        }
    }

    return (
        <div className='border-2 border-sky-200 mt-10 m-2 p-4'>

            {/* 모달 창 */}
            {result ? <ResultModal title={'처리결과'} content={result} callbackFn={closeModal}/> : <></>}




            <div className='flex justify-center mt-10'>
                <div className='relative mb-4 flex w-full flex-wrap items-stretch'>
                    <div className='w-1/5 p-6 text-right font-bold'>
                        TNO
                    </div>
                    <div className='w-4/5 p-6 rounded-r border border-solid shadow-md bg-gray-100'>
                        {todo.tno}
                    </div>
                </div>
            </div>

            <div className='flex justify-center mt-10'>
                <div className='relative mb-4 flex w-full flex-wrap items-stretch'>
                    <div className='w-1/5 p-6 text-right font-bold'>
                        WRITER
                    </div>
                    <div className='w-4/5 p-6 rounded-r border border-solid shadow-md bg-gray-100'>
                        {todo.writer}
                    </div>
                </div>
            </div>

            <div className='flex justify-center mt-10'>
                <div className='relative mb-4 flex w-full flex-wrap items-stretch'>
                    <div className='w-1/5 p-6 text-right font-bold'>
                        TITLE
                    </div>
                    <input className='w-4/5 p-6 rounded-r border border-solid border-neutral-300 shadow-md'
                        name='title'
                        type={'text'}
                        value={todo.title}
                        onChange={handleChangeTodo}
                    />
                </div>
            </div>

            <div className='flex justify-center mt-10'>
                <div className='relative mb-4 flex w-full flex-wrap items-stretch'>
                    <div className='w-1/5 p-6 text-right font-bold'>
                        DUEDATE
                    </div>
                    <input className='w-4/5 p-6 rounded-r border border-solid border-neutral-300 shadow-md'
                        name='dueDate'
                        type={'date'}
                        value={todo.dueDate}
                        onChange={handleChangeTodo}
                    />
                </div>
            </div>


            <div className='flex justify-center mt-10'>
                <div className='relative mb-4 flex w-full flex-wrap items-stretch'>
                    <div className='w-1/5 p-6 text-right font-bold'>
                        COMPLETE
                    </div>
                    <select 
                        name='status'
                        className='border-solid border-2 rounded m-1 p-2'
                        onChange={handleChangeTodoComplete}
                        value={todo.complete ? 'Y' : 'N'}>
                            
                        <option value='Y'>Completed</option> 
                        <option value='N'>Not Tet</option> 
                    </select>
                </div>
            </div>


            <div className='flex justify-end p-4'>
                <button type='button' className='rounded p-4 m-2 text-xl w-32 text-white bg-red-500'
                    onClick={handleClickDelete}>
                    DELETE
                </button>
                <button type='button' className='rounded p-4 m-2 text-xl w-32 text-white bg-blue-500'
                    onClick={handleClickModify}>
                    MODIFY
                </button>
            </div>
            
        </div>
    );
};

export default ModifyComponent;

'개발 > 연습프로젝트기록' 카테고리의 다른 글

6. 리액트와 상품 API 서버 연동  (0) 2024.04.01
5. 상품 API 서버 구성하기  (0) 2024.04.01
3. API 서버  (0) 2024.03.24
2. React-Router  (0) 2024.03.23
1. 개발 환경설정  (0) 2024.03.23