본문 바로가기

개발/React

React - 5) To-Do List

0) 설계

인턴 첫 주차 과제로 지금까지 공부한 react를 활용하여 간단한 todo list를 만드는 과제를 수행했다.

마침 react docs를 공부하면서 표를 표현하는 방법을 배웠으니, 이를 활용해서 과제를 수행하기로 했다.

조건은 크게 ①입력을 통해 todolist에 값을 추가할 수 있을 것 ② 내용을 클릭하면 취소선이 그어질 것 ③ Delete 버튼 구현이 있었다.

첫 설계는 다음과 같았다.

① todolist를 모든 함수에서 사용할 수 있도록 TODO = []를 전역변수로 선언.

② <EditableToDoList>에 insert box와 <ToDoList>를 선언하고, <ToDoList>에는 TODO의 내용을 <ToDoItem>을 반복문처럼 반환.

③ <ToDoItem>은 index, ToDo의 내용 및 취소선 여부를 저장하는 object로 구현.

처음 생각했던 구현

 

하지만, 첫 react 설계이고 명령형 언어가 아닌 선언형을 처음 다루다 보니 여러모로 문제가 많았다..

 

 

1) hierarchy 관리

React Docs에서 보았던 상품 array의 구조를 참고하여 구현하려고 했더니 Insert함수에서 TODO를 처리하는 방법을 알 수 없었다. 괜히 React에서는 data flow가 위에서 아래로 움직인다고 했던 내용도 보았었고, Insert가 TODO를 업데이트 하는 방법이 도저히 생각이 안 났던 것. 

지금까지 사용했었던 명령형 언어에서는 전역변수나 로컬변수를 내 마음대로 다룰 수 있었지만, data flow가 존재하고 state를 통해 dynamic한 변수를 관리해야한다는 점이 너무 어색하게 다가왔다. 결과적으로는 TODO를 전역변수로 선언한 것이 패착이었다. 

 

2) 전역변수에 접근하는 방법?

앞서 말한 것 처럼 가장 큰 문제는 'TODO'라는 할 일 array를 관리하는 것이었는데, insert로 update된 array를 어떻게 부모 컴포넌트인 <EditableToDoList>와 형제 컴포넌트인 <ToDoList>에 반환하는가 였다.

props로 setter를 넘겨준다.

결국 알게 된 것은 부모 컴포넌트에서 state와 setter함수를 props로 넘겨주면 자식 컴포넌트에서도 사용할 수 있다는 것이었다. 이게 아마 이번 to do list를 구현하면서 알게 된 가장 큰 내용이지 않을까.

 

 

3) Array와 Object

다른 언어들과 마찬가지로 array라는 type이 따로 존재할 줄 알았는데, array도 결국 object라고 하더라.

ToDo list를 반복문을 통해 하나씩 출력하는 코드를 처음에 이렇게 작성했었다.

 

위와 같은 오류메시지가 나타났고, 나는 array로 선언했는데 왜 object라고 하는지 이해할 수 없어서 좀 오래 해맸었다.

이는 props와 component에 대한 이해가 부족했던 탓이었다.

 

 

4) 구현

여러모로 해매면서 어떻게 state를 다른 component로 넘겨줄까 고민하다가 낸 해결책은 모든 함수를 하나의 component에 구현하는 것이었다.(...)

import { useState } from "react";
import { useCallback, useState } from "react";
import React, { Component } from "react";
import { NextPage } from "next";

let nextIdx = 0;

function EditableToDoList({ todos }) {
  const [todo, setTodo] = useState([]);
  const [inputs, setInputs] = useState("");

  const onChange = (e) => {
    setInputs(e.target.value);
  };
  const addData = (e) => {
    if (e.key === "Enter") {
      console.log(e.target.value);
      setTodo([...todo, { idx: nextIdx++, content: e.target.value, done: false }]);
      setInputs("");
      console.log(todo);
    }
  };

  return (
    <div>
      <InsertBar inputText={inputText} />
      <ToDoList todos={todos} />
      <div>
        <input
          type="text"
          value={inputs}
          placeholder=" Input here..."
          onChange={(e) => onChange(e)}
          onKeyPress={(e) => addData(e)}
        />
      </div>
      <div>
        {todo.map((todoitem) => (
          <ToDoItem todoitem={todoitem}/>
        ))}
      </div>
    </div>
  );
}

function ToDoItem({ todoitem }) {
  return (
    <div>
      <ui>{content}</ui>
      <b>- {todoitem.content}</b> <span>[Delete]</span>
    </div>
  );
}

const TODO = [
  { idx: 0, content: "First", done: false },
  { idx: 1, content: "Second", done: false },
  { idx: 2, content: "Third", done: false },
];

const Home: NextPage = () => {
  return <EditableToDoList todos={TODO} />;
};

export default Home;

참 멋대로 짠 코드이다.

 

5) component 분할

이제 이 component를 분할하는 것이 일이었다. 전역변수로 선언했던 array를 삭제한 후,  삭제했던 array와 이를 관리하는 useState를 EditableToDoList(최상위 컴포넌트)에 선언했다. 이 setter함수와 todo를 통째로 InsertBar와 ToDoList에 넘겨주었다.

 

- index.tsx

import { useCallback, useState } from "react";
import React, { Component } from "react";
import { NextPage } from "next";
import InsertBar from "../components/InsertBar";
import ToDoList from "../components/ToDoList";

const Home: NextPage = () => {
  const [todo, setTodo] = useState([]);
  return (
    <>
      <div>
        <b>ToDo</b>
        <InsertBar todo={todo} setTodo={setTodo} />
        <ToDoList todo={todo} setTodo={setTodo} />
      </div>
    </>
  );
};

export default Home;

이제 각 기능을 InsertBar 및 ToDoList에 구현하면 끝.

 

- InsertBar.tsx

import React, { useRef, useState } from "react";

let nextIdx = 0;

const InsertBar = ({ todo, setTodo }) => {
  const [inputs, setInputs] = useState("");

  const onChange = (e) => {
    setInputs(e.target.value);
  };

  const addData = (e) => {
    if (e.key === "Enter") {
      const newTodoList = todo.concat({
        idx: nextIdx++,
        content: e.target.value,
        done: false,
      });
      setTodo(newTodoList);
      setInputs("");
    }
  };

  return (
    <form onSubmit={(e) => e.preventDefault()}>
      <input value={inputs} placeholder=" Insert Here ... " onChange={onChange} onKeyPress={addData} />
    </form>
  );
};

export default InsertBar;

이때 onSubmit={(e) => e.preventDefault()를 사용하지 않으면 계속 리디렉션이 일어났다. 이는 고대 php에 있던 문제라고 하는데, 자세한 것은 추후 찾아보기로..

 

- ToDoList

import React from "react";
import ToDoItem from "./ToDoItem";
import DoneItem from "./DoneItem";

const ToDoList = ({ todo, setTodo }) => (
  <div>
    <ul>
      {todo.map((todoitem) => {
        if (todoitem.done) return <DoneItem key={todoitem.idx} todoitem={todoitem} todo={todo} setTodo={setTodo} />;
        else return <ToDoItem key={todoitem.idx} todoitem={todoitem} todo={todo} setTodo={setTodo} />;
      })}
    </ul>
  </div>
);

export default ToDoList;

원래는 ToDoItem으로 하위 component를 구성하려고 했는데, 클릭했을 때 취소선을 구현하는 부분을 어떻게 해야할 지 모르겠어서 아예 취소선을 포함한 component인 DoneItem을 추가했다.

이때 각 Item component는 클릭된 경우 본인의 boolean 값을 toggle한다.

 

import React, { useEffect, useRef, useState } from "react";

const ToDoItem = ({ todoitem, todo, setTodo }) => {
  console.log("ITEM");
  const toggleDone = () => {
    const newTodoList = todo.map((item) => ({
      ...item,
      done: item.idx === todoitem.idx ? !item.done : item.done,
    }));
    setTodo(newTodoList);
  };

  return (
    <div>
      <button onClick={toggleDone}> - {todoitem.content} </button>
      {/* DoneItem의 경우 <s>- {todoitem.content}</s> */}
      <button
        type="button"
        onClick={() => {
          setTodo(todo.filter((e) => e.idx !== todoitem.idx));
        }}
      >
        [DELETE]
      </button>
    </div>
  );
};

export default ToDoItem;

 

구현 결과

 

'개발 > React' 카테고리의 다른 글

React - 7) DOM, Rendering  (0) 2022.07.04
React - 6) To-Do List ~ Local Storage  (0) 2022.07.01
React - 4) State  (0) 2022.06.24
React - 3) Interactivity  (0) 2022.06.24
React - 2) React Design  (0) 2022.06.23