본문 바로가기

Tech/React

프론트 입문 리액트 삽질기 5 - 배열을 활용한 컴포넌트 수정, 삭제

반응형

Array, Component 수정 · 삭제

지난번 글에서 입력을 받아 카드들을 만들어 입력칸 밑에 배열로 쭈르륵 나타내 주는 페이지를 만들었습니다.

 

Array, 컴포넌트 생성

이제 이 컴포넌트에 수정, 삭제 버튼을 만들어 보려고 합니다.

이전 글에서 cardList 배열에 각 카드들의 정보를 삽입하고, CardInfoListView 컴포넌트에서 해당 배열을 받아와 CardInfo 컴포넌트로 map해 주었습니다.

//App.js
function App() {
  //...
  return (
    <Fragment>
      <h1>React Array Example</h1>
      <CardForm onCreate={(cardInfo) => handleOnCreate(cardInfo)} />
      <CardInfoListView cardList={cardList} />
    </Fragment>
  );
}


//CardInfoListView.js
function CardInfoListView({ cardList = [] }) {
  return (
    <div>
      {cardList.map(card => (<CardInfo key={card.id} info={card} />))}
    </div>
  );
}

 

CardForm에서 카드 정보를 입력받아 생성한 카드를 배열에 넣으니 카드 생성을 위한 handleOnCreate 함수를 전달해 주었으니, CardInfoListView에는 카드 수정 및 삭제를 위한 함수를 전달해 주면 되겠죠?

이 때, 배열을 직접 수정하지 않도록 splice 함수를 사용하지 않도록 주의합니다.

import { Fragment, useState } from 'react';
import './App.css';
import CardForm from './CardForm';
import CardInfoListView from './CardInfoListView';

function App() {
  //...
  
  const handleOnUpdate = (modifiedCard) => {
    setCardList(cardList.map(card => (card.id === modifiedCard.id) ? modifiedCard : card))
  }

  const handleOnDelete = (cardId) => {
    setCardList(cardList.filter(card => card.id !== cardId))
  }

  return (
    <Fragment>
      <h1>React Array Example</h1>
      <CardForm onCreate={(cardInfo) => handleOnCreate(cardInfo)} />
      <CardInfoListView cardList={cardList} handleOnUpdate={handleOnUpdate} handleOnDelete={handleOnDelete} />
    </Fragment>
  );
}

export default App;

 

handleOnUpdate 함수는 map 메소드를 사용해 cardList 내에서 id가 같은 카드를 찾아 내용을 바꿔치기합니다.

handleOnDelete 함수는 filter 메소드를 사용해 cardList 안에서 id가 같지 않은 카드들만 남겨둡니다.

 

넘겨진 함수들은 CardInfoListView 컴포넌트에서 다시 CardInfo 컴포넌트로 넘겨집니다.

import CardInfo from "./CardInfo";

function CardInfoListView({ cardList = [], handleOnUpdate, handleOnDelete }) {
    return (
        <div>
            {cardList.map(card => (<CardInfo key={card.id} info={card} handleOnUpdate={handleOnUpdate} handleOnDelete={handleOnDelete} />))}
        </div>
    );
}

export default CardInfoListView

 

그리고 넘겨진 함수들은 CardInfo 컴포넌트에서 사용됩니다.

import { Fragment, useEffect, useState } from "react";

function CardInfo({ info = { id: 0, name: "N/A", description: "N/A" }, handleOnUpdate, handleOnDelete }) {
    const [editing, setEditing] = useState(false)
    const [name, setName] = useState(info.name)
    const [description, setDescription] = useState(info.description)

    useEffect(() => {
        console.log("CardInfo component created.")
        return () => console.log("cardInfo component destroyed.")
    }, [])

    return (
        <div style={{
            border: '1px solid black',
            padding: '8px',
            margin: '8px'
        }}>
            {
                editing
                    ?
                    <form onSubmit={e => { e.preventDefault(); setEditing(false); handleOnUpdate({ id: info.id, name: name, description: description }) }} >
                        <input type="text" value={name} name="cardName" placeholder="name" onChange={e => setName(e.target.value)} />
                        <input type="text" value={description} name="cardDescription" placeholder="description" onChange={e => setDescription(e.target.value)} />
                        <button type="submit"> Submit </button>
                        <button onClick={e => setEditing(false)}> Cancel </button>
                    </form>
                    :
                    <Fragment>
                        <div><b>{info.name}</b></div>
                        <div>{info.description}</div>
                        <button onClick={e => setEditing(true)}>Edit</button>
                        <button onClick={e => handleOnDelete(info.id)}>Delete</button>
                    </Fragment>
            }
        </div>
    );
}

export default CardInfo

 

App, CardInfoListView 컴포넌트에 비해 많은 변화가 있었는데, 하나씩 살펴보겠습니다.

props에 handleOnUpdate, handleOnDelete 함수를 받도록 수정해 주었고, editing, name, description state hook이 추가되었습니다.

또한, 지난번에 LifecycleAPI를 다룰 때 컴포넌트를 삭제하는법을 몰라 동작을 확인하지 못했던 componentWillUnmount 의 Effect Hook 스타일 버전을 확인해 보기 위해 빈 배열을 dependency로 갖는 Effect Hook에 return문을 추가했습니다.

 

프론트 입문 리액트 삽질기 3 - Effect Hook으로 Lifecycle API 동작 구현하기

클래스형 컴포넌트에서 사용하던 state는 함수형 컴포넌트에서 State Hook으로 대체하여 사용할 수 있었습니다. 이제 특정 동작을 수행(?) 했을 때 동작하는 함수를 추가해 보려고 합니다. 기존 클래

sean-ma.tistory.com

useEffect(() => {
    console.log("CardInfo component created.")
    return () => console.log("cardInfo component destroyed.")
}, [])

 

그리고 jsx 내에서 editing 변수의 state에 따라 동작하도록 자바스크립트를 삽입해 주었습니다.

editing
    ?
        <form onSubmit={e => { e.preventDefault(); setEditing(false); handleOnUpdate({ id: info.id, name: name, description: description }) }} >
            <input type="text" value={name} name="cardName" placeholder="name" onChange={e => setName(e.target.value)} />
            <input type="text" value={description} name="cardDescription" placeholder="description" onChange={e => setDescription(e.target.value)} />
            <button type="submit"> Submit </button>
            <button onClick={e => setEditing(false)}> Cancel </button>
        </form>
    :
        <Fragment>
            <div><b>{info.name}</b></div>
            <div>{info.description}</div>
            <button onClick={e => setEditing(true)}>Edit</button>
            <button onClick={e => handleOnDelete(info.id)}>Delete</button>
        </Fragment>

 

기본적으로 editing의 state는 false로 지정되어 있으니 기존에 보여지던 아래쪽 뷰가 화면에 띄워집니다.

Delete 버튼 클릭 시 바로 handleOnDelete 함수를 호출해 주고, Edit 버튼 클릭 시에는 editing state를 true로 지정해 form을 화면에 띄워줍니다.

마찬가지로 form submit 시에 페이지 새로고침을 막기 위해 onSubmit 시에 preventDefault를 호출해준 뒤, editing 모드를 false로 설정해 준 다음 handleOnUpdate 함수를 호출해 줍니다.

 

업데이트는 다음과 같이 동작합니다.

Update

Delete는 다음과 같이 동작합니다.

Delete

오른쪽 콘솔창을 보면 Effect Hook을 사용해 구현한 componentWillUnmount 동작도 확인하실 수 있습니다.

 

+추가 : 컴포넌트의 생성과 삭제는 다음과 같이 이루어집니다. key 값을 확인해 주세요.

React Debug Tool

반응형