상세 컨텐츠

본문 제목

[React.js 1팀] Project 2 [할 일 관리] 앱 만들기 2 (Read: 할 일 리스트 렌더링하기 ~ Delete: 할 일 삭제하기)

24-25/React.js 1

by mingging17 2025. 1. 3. 10:00

본문

728x90

 

저번 할 일 관리 앱 만들기- 할 일 추가하기에 이어서 이번에는 할 일 리스트를 렌더링, 수정, 삭제 하는 법에 대해 알아보도록 하겠다.

 

1. Read: 할 일 리스트 렌더링하기

 Read 기능을 이용하면 배열에 저장한 할 일 아이템을 여러 개 반복해서 페이지에 렌더링할 수 있다. 배열 todoTodoList 컴포넌트에 Props로 전달한다.

<TodoList /> <TodoList todo={todo}/>

 

 TodoList 컴포넌트에서는 App에서 Props로 전달된 todo를 리스트로 렌더링해야 한다. 이때 배열 메서드 map을 이용해서 HTML이나 컴포넌트를 순회하면서 매 요소를 반복하며 렌더링할 수 있다.

 

 아래는 배열 메서드 map을 이용해서 HTML 요소를 반복해 렌더링한 코드이다. <TodoItem />을 삭제하고 map을 삽입하였다. 그리고 TodoItem 컴포넌트에 전달된 Props를 이 컴포넌트에서 사용할 수 있도록 TodoItem.js도 수정한다.

 

<div className="TodoList">
        <h4>Todo List 🌱</h4>
        <input className="searchbar"placeholder="검색어를 입력하세요"/>
        <div className="list_wrapper">
            {todo.map((it)=>(
                <TodoItem {...it}/>
            ))}
        </div>
    </div>
<div className="TodoItem">
<div className="checkbox_col">
<input checked={isDone} type="checkbox" />
</div>
<div className="title_col">{content}</div>
<div className="date_col">{new Date(createdDate).toLocaleDateString()}</div>
<div className="btn_col">
<button>삭제</button>
</div>
</div>

 

 실행 시 아래 그림처럼 공부하기’,‘독서하기아이템을 추가할 수 있다.

 

 

 

 

 

1-(1) key 설정하기

 key, 리스트에서 컴포넌트를 구분하기 위해 사용하는 값이다. 특정 컴포넌트를 수정, 추가, 삭제할 경우 이 key로 어떤 컴포넌트에 업데이트할지 결정하게 된다. 성능을 높이거나 효율적으로 탐색하기 위해서는 key로 구분해 주는 것이 좋다.

 이번에는 고유 idkey로 전달해 보도록 하겠다. 설정 후 콘솔 탭에 key가 새로 생긴 걸 볼 수 있다.

<div className="list_wrapper">
{todo.map((it)=>(
<TodoItem key={it.id} {...it} />
))}
</div>

 

 이처럼 map을 이용해 컴포넌트를 리스트 형태로 반복적으로 렌더링하려면 리스트 내 고유한 keyProps로 전달해야 한다.

 

 

1-(2) 검색어에 따라 필터링하기

 이 파트에서는 검색 폼에서 검색어를 입력하면 해당 문자열을 포함하는 아이템만 필터링해서 보여주는 기능을 구현해 보겠다.

 TodoList.js를 먼저 수정한다. 우선, react 라이브러리에서 useState 리액트 훅을 불러온 뒤, 아래 첫 번째 코드와 같이 수정한다. 그 후 함수 getSearchResult를 이용해서, search가 빈 문자열 (“”)이면 todo를 그대로 반환하고, 그렇지 않으면 todo배열에서 search의 내용과 일치하는 아이템만 필터링해 반환하도록 수정한다.

 두번째 코드처럼 마지막으로 getSearchResult의 결괏값을 map 메서드를 이용해 리스트로 렌더링한다.

const TodoList = ({todo}) => {
    const [search, setSearch] = useState(""); 
    const onChangeSearch = (e) => {
    setSearch(e.target.value);
    };
(...)
<input 
        value={search}
        onChange={onChangeSearch}
        className="searchbar"
        placeholder="검색어를 입력하세요" 
        />
const getSearchResult = () => {
        return search === ""
        ? todo
        : todo.filter((it) => it.content.includes(search));
    };
(...)
<div className="list_wrapper"> 
            {getSearchResult().map((it)=>(
                <TodoItem key={it.id} {...it} />
            ))}
        </div>

 

이제 검색 기능이 만들어졌다.

 

 하지만 아직 대소문자 구별하지 못하기 때문에, 첫 번째 처럼 코드를 삽입해서 대소문자를 구별하지 않도록 수정해야 한다. toLowerCase() 메서드는 문자열에 있는 모든 대문자를 소문자로 바꿔주는 기능을 한다.

const getSearchResult = () => {
return search === ""
? todo
: todo.filter((it) =>
it.content.toLowerCase().includes(search.toLowerCase()));
};

 

 

 

 

2. Update: 할 일 수정하기

 할 일을 생성했으면, 이제 완료한 할 일을 체크하는 기능을 만들 차례이다. 이번에는 함수 onUpdate를 생성해서 TodoList 컴포넌트에 Props로 전달하는 코드를 만들어보자. 이어서, TodoList에서 TodoItem 컴포넌트에 함수 onUpdate를 전달하는 코드를 만들어보자. 첫 번째 코드는 src/App.js에 삽입한 코드이고 두 번째 코드는 src/component/TodoList.js에서 수정한 코드이다.

const onUpdate = (targetId) => {
    setTodo(
      todo.map((it) =>
        it.id === targetId ? { ...it, isDone: !it.isDone } : it
      )
    );
};
const TodoList = ({todo, onUpdate}) =>
(...)
<div className="list_wrapper"> 
            {getSearchResult().map((it)=>(
                <TodoItem key={it.id} {...it} onUpdate={onUpdate} />
            ))}
        </div>

 

 현재 작성한 코드의 TodoListTodoItem 컴포넌트에 함수 onUpdate를 전달해야 하므로 Props로 받아 다시 전달하는 일종의 매개 역할을 수행하게 된다.

 리액트 컴포넌트는 바로 한 단계 아래의 자식 컴포넌트에만 데이터를 전달할 수 있다. 한 단계 이상 떨어져 있는 자식 컴포넌트에 데이터를 전달하기 위해서, 어쩔 수 없이 전달에 전달을 반복해야 한다. 위와 같은 현상을 ‘Props가 마치 땅을 파고 내려가는 것 같다라고 하여, ‘Props Drilling이라고 부르게 된다. 다소 비효율적인 방식이지만, 현재 파트에서는 Props Drilling으로 코드를 생성하였다.

 

 

2-(1) TodoItem 컴포넌트에서 아이템 수정 함수 호출

이제 TodoItemTodoList한테 전달받아서 onUpdate를 호출할 차례이다.

const TodoItem = ({ id, content, isDone, createdDate, onUpdate }) => { 
    const onChangeCheckbox = () => { 
    onUpdate(id);
};
return (
    <div className="TodoItem">
    <div className="checkbox_col">
        <input onChange={onChangeCheckbox}  
				checked={isDone} type="checkbox" />
    </div>
    <div className="title_col">{content}</div>
    <div className="date_col">
        {new Date(createdDate).toLocaleDateString()}
    </div>
    <div className="btn_col">
        <button>삭제</button>
    </div>
    </div>
);
};

 

 Props를 구조 분해 할당한 후 함수 onUdate를 추가한다. 호출할 onChangeCheckbox 함수를 만들어서, onUpdate를 호출하고 인수로 현재 체크가 발생한 할 일 아이템의 id를 전달하도록 한다. 이후 체크박스 입력 폼의 onChage 이벤트 핸들러를 함수 onChangeCheckbox로 설정한다.

 

 모든 코드가 올바르게 적용이 되었다면, 아래 그림처럼 체크박스를 체크했을 때 완료 여부를 표시하는 체크 표시가 나타나며 Components 탭에 isDone 프로퍼티가 true로 변경될 것이다.

 

 

 

 

3. Delete: 할 일 삭제하기

 지금은 리스트 바로 옆에 있는 삭제버튼을 클릭해도 할 일이 삭제되지 않는다. 체크 기능과 유사한 흐름으로 삭제기능을 제작해 보겠다.

 

 

3-(1) 아이템 삭제 함수

App 컴포넌트에서 할 일을 삭제하는 함수 onDelete를 만든다.

const onDelete = (targetId) => { 
  setTodo(todo.filter((it) => it.id !== targetId));
};

  return (
    <div className="App">
      <Header />
      <TodoEditor onCreate={onCreate}/>
      <TodoList todo={todo} onUpdate={onUpdate} onDelete={onDelete}/>
    </div>
  );
}

 

 ‘삭제버튼을 클릭하면 onDelete 함수가 호출이 되고, onDelete는 매개변수 targetId에 삭제 할 일 아이템의 id를 저장하게 된다. 해당 id 요소를 뺀 새 배열로 todo를 업데이트 함으로써 대상 아이템을 삭제할 수 있다.

 

 그 후, Props로 받은 함수 onDelete를 다시 TodoItem 컴포넌트에 전달하는 코드를 작성한다. Props를 구조 분해 할당하고 함수 onDelete를 추가한다.

const TodoList = ({todo, onUpdate, onDelete}) =>
<div className="list_wrapper"> 
            {getSearchResult().map((it)=>(
                <TodoItem 
                key={it.id} {...it} 
                onUpdate={onUpdate}
                onDelete={onDelete} 
                />
            ))}
        </div>

 

 

3-(2) TodoItem 컴포넌트에서 삭제 함수 호출하기

이제 마지막으로 TodoItem에서 삭제 버튼을 클릭하면 함수 onDelete를 호출하도록 구현하자.

const TodoItem = ({ id, content, isDone, createdDate, onUpdate, onDelete }) => { 
    const onChangeCheckbox = () => { 
    onUpdate(id);
};
    const onClickDelete = () => { 
    onDelete(id);
};
<div className="btn_col">
        <button onClick={onClickDelete}>삭제</button>
    </div>

 

 Props를 구조 분해 할당하고, 삭제 버튼을 클릭하면 호출할 함수 onClickDelete를 만든다. 위와 같이, 이제 삭제 버튼을 누르면 함수 onDelete를 호출하고 인수로 해당 아이템의 id를 전달해서 삭제할 수 있게 된다.

 

 ‘react 공부하기삭제버튼을 클릭하게 되면 공부하기 리스트가 사라진다. 또한 콘솔 창을 확인했을 때도 사라진 것을 볼 수 있다.

 


출처 :  이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023).

Corner React.js 1

Editor: IJin

728x90

관련글 더보기