본문 바로가기
💻CODING/react. vue

[react] 리액트 테이블 게시판 만들기 ver.2 (데이터 추가, 수정, 저장, hooks, form)

by 코딩하는 갓디노 2021. 4. 13.

react 테이블 게시판 구현하기

 

구현내용
· npm package: creat-react-app
· css 라이브러리: tailwind
· axios를 통한 서버 통신
· 테이블 형식의 게시판 구현 
· 데이터 추가/수정/삭제 기능 추가
· 모달 팝업창 오픈

 

구현 화면

 

이전 시간에 이어서 폼을 삽입하여 새로운 데이터를 추가하고,
수정 버튼을 클릭하면 모달창을 띄워서 해당 데이트를 바로 수정하고, 저장하는 기능을 알아보겠습니다. 

 

데이터 추가하기

onChange, onSubmit, spead 연산자/concat

Form, input 태그를 사용하여 만들었고, input name, value, onChange 속성을 만들어 onChange 이벤트로 입력할때 마다 각각의 name 속성에 value 값을 넣어줍니다.

 

Post.js (하위1 컴포넌트, 고객 정보 추가하기 form)

import React, { useState } from 'react';

const Post = ({ onSaveData }) => {
    const [form, setForm] = useState({
        name: '',
        email: '',
        phone: '',
        website: '',
    });

    const handleChange = (e) => {
        const { name, value } = e.target;
        setForm({
            ...form,
            [name]: value
        })
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        onSaveData(form)
        console.log(form);
        setForm({
            name: '',
            email: '',
            phone: '',
            website: '',
        })
    }

    return (
        <>
            <div className='text-xl font-bold mt-5 mb-2 text-center'>고객 추가하기</div>
            <form className="mt-3" onSubmit={handleSubmit}>
                <div className="flex flex-col md:flex-row mb-1">
                    <label htmlFor="username" className="w-full flex-1 mx-2 text-xs font-semibold 
                    text-gray-600 uppercase">Name
                        <input className="w-full py-3 px-1 mt-1 
                    text-gray-800 appearance-none 
                    border-2 border-gray-100
                    focus:text-gray-500 focus:outline-none focus:border-gray-200"
                            required placeholder='이름을 입력해주세요' type='text' name='name' 
                            value={form.name} onChange={handleChange} />
                    </label>
                    <label htmlFor="email" className="w-full flex-1 mx-2 text-xs font-semibold 
                    text-gray-600 uppercase">Email
                        <input className="w-full py-3 px-1 mt-1 
                    text-gray-800 appearance-none 
                    border-2 border-gray-100
                    focus:text-gray-500 focus:outline-none focus:border-gray-200"
                            required placeholder='이메일 주소를 입력해주세요' type='email' name='email' 
                            value={form.email} onChange={handleChange} />
                    </label>
                </div>
                <div className="flex flex-col md:flex-row">
                    <label htmlFor="phone" className="w-full flex-1 mx-2 text-xs font-semibold 
                    text-gray-600 uppercase">Phone
                        <input className="w-full py-3 px-1 mt-1 
                    text-gray-800 appearance-none 
                    border-2 border-gray-100
                    focus:text-gray-500 focus:outline-none focus:border-gray-200"
                            required placeholder='핸드폰 번호를 입력해주세요' type='text' name='phone' 
                            value={form.phone} onChange={handleChange} />
                    </label>
                    <label htmlFor="website" className="w-full flex-1 mx-2 text-xs font-semibold 
                    text-gray-600 uppercase">Website
                        <input className="w-full py-3 px-1 mt-1 
                    text-gray-800 appearance-none 
                    border-2 border-gray-100
                    focus:text-gray-500 focus:outline-none focus:border-gray-200"
                            required placeholder='사이트 주소를 입력해주세요' type='text' name='website' 
                            value={form.website} onChange={handleChange} />
                    </label>
                </div>
                <div className='text-center'>
                    <button className='bg-blue-400 py-2 text-center px-10 md:px-12 md:py-3 text-white 
                    rounded text-xl md:text-base mt-4' type='submit'>저장</button>
                </div>
            </form>
        </>
    );
};

export default Post;

 

Post.js 폼 화면

고객 정보를 입력하는 form이 나오고, 입력 후 저장을 누르면 위의 테이블에 데이터가 추가됩니다. 

 

저장 후 아래에 추가된 리스트 화면

 

데이터 추가 기능

input의 onChange 이벤트를 handleChange  함수로 넘깁니다.

<Post.js>

const handleChange = (e) => {
  const { name,value } = e.target;
  setForm({
    ...form,
    [name]: value
  })
};

form의 onSubmit 이벤트를  handleSubmit 함수로 넘기고, 상위의  onSaveData 함수에  입력된 form 객체를 전달해 줍니다. 

const handleSubmit = (e) => {
  e.preventDefault();
  onSaveData(form)
  setForm({ //초기화
    name: '',
    email: '',
    phone: '',
    website: '',
  })
}

 

react에서는 데이터를 추가할때 불변성의 이유로 push를 사용하지 않고, spead 연산자 또는 concat을 이용하여 추가합니다.

 

데이터 추가하는 방법1 : spread 연산자

form 객체를  data를 받아서, 기존의 값은 prev 담고, 변경된 값만 업데이트 하여 새로운 배열을 리턴합니다.

<Board.js>

const handleSave = (data) => {
  setInfo((prev) => {
    return [...prev, {
      id: nextId.current,
      name: data.name,
      email: data.email,
      phone: data.phone,
      website: data.website
    }]
  });
  nextId.current += 1;
}

추가가 배열의 앞부터 되길 원할 경우, ...prev를 뒤에 넣어줍니다.

const handleSave = (data) => {
  setInfo((prev) => {
    return [{
      id: nextId.current,
      name: data.name,
      email: data.email,
      phone: data.phone,
      website: data.website
    }, ...prev]
  });
  nextId.current += 1;
}

 

데이터 추가하는 방법2: concat

concat() 메서드는 매개변수로 전달된 모든 문자열을 호출 문자열에 붙인 새로운 문자열을 반환합니다.

const handleSave = (data) => {
  setInfo(info => info.concat({
    id: nextId.current,
    name: data.name,
    email: data.email,
    phone: data.phone,
    website: data.website
  }))
  nextId.current += 1;
}

 

데이터 수정하기

onChange, onSubmit, map

수정 버튼 클릭시 데이터 추가하기와 같은 방법으로 onChange, onSubmit 이벤트로 데이터를 수정합니다.

Modal.js (하위1 컴포넌트, 고객 정보 수정하기 팝업창)

import React, { useState } from 'react';

const Modal = ({ selectedData, handleCancel, handleEditSubmit }) => {
  const [edited, setEdited] = useState(selectedData);

  const onCancel = () => {
    handleCancel();
  }

  const onEditChange = (e) => {
    setEdited({ //문법
      ...edited,
      [e.target.name]: e.target.value
    })
  }

  const onSubmitEdit = (e) => {
    e.preventDefault();
    handleEditSubmit(edited);
  }

  return (
    <div className="h-screen w-full fixed left-0 top-0 flex justify-center items-center 
    bg-black bg-opacity-70">
      <div className="bg-white rounded shadow-lg w-10/12 md:w-1/3">
        <div className="border-b px-4 py-2 flex justify-between items-center">
          <h3 className="font-semibold text-lg">고객 정보 수정하기</h3>
          <i className="fas fa-times cursor-pointer" onClick={onCancel}></i>
        </div>
        <form onSubmit={onSubmitEdit}>
          <div class="p-3">

            <div>ID: {edited.id}</div>
            <div>Name: <input className='border-2 border-gray-100' type='text' name='name' 
            value={edited.name} onChange={onEditChange} /></div>
            <div>Email: <input className='border-2 border-gray-100' type='text' name='email' 
            value={edited.email} onChange={onEditChange} /></div>
            <div>Phone: <input className='border-2 border-gray-100' type='text' name='phone' 
            value={edited.phone} onChange={onEditChange} /></div>
            <div>Website: <input className='border-2 border-gray-100' type='text' 
            name='website' value={edited.website} onChange={onEditChange} /></div>

          </div>
          <div className="flex justify-end items-center w-100 border-t p-3">
            <button className="bg-red-600 hover:bg-red-700 px-3 py-1 rounded text-white 
            mr-1 close-modal" onClick={onCancel}>취소</button>
            <button type='submit' className="bg-blue-600 hover:bg-blue-700 px-3 py-1 
            rounded text-white">수정</button>
          </div>
        </form>
      </div>
    </div>
  );
};

export default Modal;

 

모달창 오픈

조건부 렌더링을 이용하여 modalOn이 true일 경우, 모달 컴포넌트를 작동시킵니다. 

const [modalOn, setModalOn] = useState(false);
...
return (
 {modalOn && <Modal ... />}
)

 

모달창 화면

 

데이터 수정, 저장 기능

클릭한 해당 데이터(이전) 모달창에 표시

<td> 태그의 수정 버튼 클릭시 모달창이 오픈되고, 클릭된 해당 user 데이터를 form으로 표시합니다.

버튼 클릭시 onEdit 함수를 호출하고, 상위의 handleEdit 함수를 실행하면서 해당 user 데이터를 보내줍니다.

<Td.js>

const Td = ({item, handleRemove, handleEdit}) => {
    const onEdit = () => {
        handleEdit(item);
    }

    return (
            <td onClick={onEdit}><i class="far fa-edit"></i></td>
    )
};

<Modal.js>

const handleEdit = (item) => {
  setModalOn(true);
  const selectedData = {
    id: item.id,
    name: item.name,
    email: item.email,
    phone: item.phone,
    website: item.website
  };
  console.log(selectedData);
  setSelected(selectedData);
};

 

데이터 수정

onChage와 onSubmit 메서드를 이용하는 데이터 추가하기와 같은 방법입니다.

onSubmitEdit 함수를 실행하고, 수정된 data를 handleEditSubmit 함수에 넣어 실행합니다.

<Modal.js>

const onEditChange = (e) => {
  setEdited({ //문법
    ...edited,
    [e.target.name]: e.target.value
  })
}

const onSubmitEdit = (e) => {
  e.preventDefault();
  handleEditSubmit(edited);
}

return (
        <form onSubmit={onSubmitEdit}>
            <div>ID: {edited.id}</div>
            <div>Name: <input type='text' name='name' 
            value={edited.name} onChange={onEditChange} /></div>
            <div>Email: <input type='text' name='email' 
            value={edited.email} onChange={onEditChange} /></div>
            <div>Phone: <input type='text' name='phone' 
            value={edited.phone} onChange={onEditChange} /></div>
            <div>Website: <input type='text' name='website' value={edited.website} 
            onChange={onEditChange} /></div>
            
            <button type='submit'>수정</button>
        </form>
  );

 

handleEditSubmit 함수에서 수정된 데이터를 item으로 받아
기존의 데이터 추가하기의 handleSave 함수를 이용하여 조건부 렌더링을 합니다.

Array.map을 사용하여 변경할 user의 데이터만 업데이트시키고, 나머지 user 데이터를 그대로 출력합니다.

<Board.js>

const handleEditSubmit = (item) => {
  console.log(item);
  handleSave(item);
  setModalOn(false);
}

const handleSave = (data) => {
  //데이터 수정하기
  if (data.id) { //받아온 데이터 id가 있을 경우,
    setInfo(
      info.map(row => data.id === row.id ? { //가져온 id가 기존 table id가 같으면 
        //가져온 데이터 반영
        id: data.id,
        name: data.name,
        email: data.email,
        phone: data.phone,
        website: data.website,
      } : row))
  } else { 
    //기존의 데이터 추가하기 코드
  }
}
확인

 

Board.js(부모 컴포넌트, table)

import React, { useEffect, useState, useRef } from "react";
import axios from 'axios';
import Tr from './Tr';
import Post from './Post';
import Modal from './Modal';

const Board = () => {
  const [info, setInfo] = useState([]);
  const [selected, setSelected] = useState('');
  const [modalOn, setModalOn] = useState(false);

  // 고유 값으로 사용 될 id
  // ref 를 사용하여 변수 담기
  const nextId = useRef(11);

  useEffect(() => {
    axios.get('https://jsonplaceholder.typicode.com/users')
      .then(res => setInfo(res.data))
      .catch(err => console.log(err));
  }, []);

  const handleSave = (data) => {
    //데이터 수정하기(중요)
    if (data.id) {
      setInfo(
        info.map(row => data.id === row.id ? {
          id: data.id,
          name: data.name,
          email: data.email,
          phone: data.phone,
          website: data.website,
        } : row))

    } else {
      // 데이터 추가하기 방법1
      // setInfo((prev) => {
      //   return [ ...prev, {
      //     id: nextId.current,
      //     name: data.name,
      //     email: data.email,
      //     phone: data.phone,
      //     website: data.website
      //   }]
      // });

      //데이터 추가하기 방법2
      setInfo(info => info.concat(
        {
          id: nextId.current,
          name: data.name,
          email: data.email,
          phone: data.phone,
          website: data.website
        }
      ))
      nextId.current += 1;
    }
  }

  const handleRemove = (id) => {
    setInfo(info => info.filter(item => item.id !== id));
  }

  const handleEdit = (item) => {
    setModalOn(true);
    const selectedData = {
      id: item.id,
      name: item.name,
      email: item.email,
      phone: item.phone,
      website: item.website
    };
    console.log(selectedData);
    setSelected(selectedData);
  };

  const handleCancel = () => {
    setModalOn(false);
  }

  const handleEditSubmit = (item) => {
    console.log(item);
    handleSave(item);
    setModalOn(false);
  }

  return (
    <div className="container max-w-screen-lg mx-auto">
      <div className='text-xl font-bold mt-5 mb-3 text-center'>고객 정보 리스트</div>
      <table className="min-w-full table-auto text-gray-800">
        <thead className='justify-between'>
          <tr className='bg-gray-800'>
            <th className="text-gray-300 px-4 py-3">Id.</th>
            <th className="text-gray-300 px-4 py-3">Name</th>
            <th className="text-gray-300 px-4 py-3">Email</th>
            <th className="text-gray-300 px-4 py-3">Phone No.</th>
            <th className="text-gray-300 px-4 py-3">Website</th>
            <th className="text-gray-300 px-4 py-3">Edit</th>
            <th className="text-gray-300 px-4 py-3">Delete</th>
          </tr>
        </thead>
        <Tr info={info} handleRemove={handleRemove} handleEdit={handleEdit} />
      </table>
      <Post onSaveData={handleSave} />
      {modalOn && <Modal selectedData={selected} handleCancel={handleCancel} 
      handleEditSubmit={handleEditSubmit} />}
    </div>
  );
};

export default Board;

 

수정전 화면

 

수정 후 화면

반응형

댓글