Updated:

중간고사 저번주에 끝나고.. 바로 제 주전공 프로젝트 과목 2개를 바로 시작해야 해서… 틈틈히 해봤는데 너무 시간이 부족해서 지금 까지 만든 것을 제출하고 다시 천천히 해볼려고 합니다..

지금 까지 만든 웹은 다음과 같다.

초기 상태의 App.js는 다음과 같고 App.css는 빈 파일이다.

import './App.css';

function App() {
  return (
    <div className='App'>
    </div>
  );
}
export default App;

Inputfield

먼저 할 일 아이템을 생성하는 Inputfield 컴포넌트를 만들고 App.jsInputfield.js를 다음과 같이 입력한다.

import './App.css';
import Inputfield from './component/Inputfield';

function App() {
  return (
    <div className='App'>
      <Inputfield />
    </div>
  );
}
export default App;
import "./Inputfield.css";

const Inputfield = () => {
  return <div className="Inputfield"> </div>;
};
export default Inputfield;

Inputfield.js에서 먼저 UI를 만든다.

import "./Inputfield.css";

const Inputfield = () => {
  return (
    <div className="Inputfield">
      <h1>Things to do</h1>
      <div className="inputbox">
        <input placeholder="Enter your Todo" />
        <button>+</button>
      </div>
    </div>
  );
};
export default Inputfield;

Inputfield.css는 다음과 같이 만들었다.

.Inputfield {
/*  margin-bottom: 10px; */
  margin: 0 auto;
  padding-bottom: 10px;
}

.Inputfield .inputbox {
  width: 100%;
  display: flex;
  gap: 10px;
}

.Inputfield input {
  flex: 1;
  box-sizing: border-box;
  border: 1px solid rgb(0,0,0);
  border-radius: 5px;
  padding: 5px;
}

/* .Inputfield input:focus {
  outline: none;
  border: 1px solid #1f93ff;
}
마우스로 박스를 클릭하면 #1f93ff로 반짝임 */

.Inputfield button {
  cursor: pointer;
  width: 30px;
  border: none;
  background-color: rgb(245,245,245);
  color: rgb(0,0,0);
  border-radius: 5px;
}

Todo, Done

먼저 Todo, Done 컴포넌트를 만들고 Todo.js, Todo.css, Done.js, Done.css를 생성한다. Todo.js, Done.js를 다음과 같이 입력하고 App.js를 수정한다.

import "./Todo.css";

const Todo = () => {
  return(
    <h1>Todo</h1>
      <div className="Todo_list">
      </div>
  );
};
export default Todo;
import "./Todo.css";

const Done = () => {
  return(
    <h1>Done</h1>
      <div className="Done_list">
      </div>
  );
};
export default Done;
import './App.css';
import Inputfield from './component/Inputfield';
import Todo from "./component/Todo";
import Done from './component/Done';

function App() {
  return (
    <div className='App'>
      <Inputfield />
       <section>
        <Todo />
      </section>
      <section>
        <Done />
      </section>
    </div>
  );
}
export default App;

TodoItem, DoneItem

다음과 같이 Todo와 Done에서 할 일 item을 표현하는 TodoItem, DoneItem 컴포넌트를 만든다.

TodoItem.js, DoneItem.js, TodoItem.css, DoneItem.css를 생성한 후 다음과 같이 입력한다. Todo와 Done은 거의 비슷하므로 하나만 나타낸다.

import "./TodoItem.css";

const TodoItem = () => {
  return (
    <div className="TodoItem">
      <div className="checkbox">
        <input type="checkbox" />
      </div>
      <div className="title"> </div>
      <div className="DeleteButton">
        <button onClick={onClickDelete}>삭제</button>
      </div>
    </div>
  );
};
export default TodoItem;
.TodoItem {
  margin: 0 auto;
  display: flex;
  align-items: center;
/*  padding-bottom: 20px; */
}

.TodoItem .title {
  padding: 10px;
  flex: 1;
}

.TodoItem .DeleteButton button {
  cursor: pointer;
  color: rgb(0,0,0);
  border: none;
  border-radius: 5px;
  padding: 5px;
}

이후 Todo에 Todolist 컴포넌트를 배치한다. 다음과 같이 Todo.js를 수정한다.

import "./Todo.css";

const Todo = () => {
  return(
    <h1>Todo</h1>
      <div className="Todo_list">
        <TodoItem />
      </div>
  );
};
export default Todo;

기능 구현하기

지금 까지 UI를 구현 하였으므로 이제 기능을 구현 한다.

기초 데이터 설정

할 일 item을 생성 하기위해 다음과 같이 App.js를 수정한다.

import {useState} from "react";
(...)

function App() {
  const [todo, setTodo] = useState([]);
  return (
    (...)
  );
}
export default App;

onCreate

먼저 Inputfield에서 +버튼을 누르면 새로운 할 일 item을 추가하는 함수 onCreate를 만든다. 또한 추가된 item의 id를 서로 다르게 해주기 위해 Ref 객체를 사용하였다. App.js를 다음과 같이 수정한다.

import {useState, useRef} from "react"
(...)

function App() {
  const idRef = useRef(3);

  const [todo, setTodo] = useState([]);

  const onCreate = (content) => {
    const newItem = {
      id: idRef.current,
      content,
      isDone: false,
    };
    setTodo([newItem, ...todo]);
    idRef.current +=1;
  };

  return (
    <div className='App'>
      <Inputfield onCreate={onCreate}/> {/* +버튼을 클릭해야 새로운   item 생성 */}
      (...)
    <div>
  );
}
export default App;

다음으로 onCreate를 사용하기 위해 Inputfield 컴포넌트를 수정한다. Inputfield.js는 다음과 같다.

import "./Inputfield.css";

const Inputfield = ({onCreate}) => {
  const [content, setContent] = useState("");

  const onChangeContent = (e) => {
    setContent(e.target.value);
  }; // 할 일 데이터를 저장할 State

  const onSubmit = () => {
    onCreate(content);
  } // + 버튼을 누르면 onCreate 함수 호출

  return (
    <div className="Inputfield">
      <h1>Things to do</h1>
      <div className="inputbox">
        <input placeholder="Enter your Todo" />
        <button>+</button>
      </div>
    </div>
  );
};
export default Inputfield;

지금 까지 만든 Create 기능의 완성도를 높이기 위해 다음과 같은 기능을 추가 하였다.

  • 빈 입력 방지하기
  • item 추가 후 입력 폼 초기화 하기
  • Enter 키를 눌러 아이템 추가하기

Inputfield.js를 다음과 같이 수정한다.

const Inputfield = ({onCreate}) => {
  (...)
  const onSubmit = () => {
    if(!content) {
      inputRef.current.focus();
      return
    } // 빈칸이면 추가 안됨
    onCreate(content);
    setContent(""); // 추가를 하면 inputbox 초기화
  }

  const onKeyDown = (e) => {
    if(e.keyCode === 13) { // 13은 Enter을 의미
      onSubmit();
    }
  }; // Enter 누르면 실행

  return (
    (...)
    <input
      ref={inputRef}
      value={content}
      onChange={onChangeContent}
      onKeyDown={onKeyDown} // Enter 누르면 실행
      (...)
    />
    (...)
  )
(...)
}
export default Inputfield;

map을 이용해 배열 데이터를 렌더링

App 컴포넌트의 State 변수 todo에는 배열 형태로 여러 개의 할 일 아이템이 저장되어 있다. 배열 todo를 TodoList 컴포넌트에 Props로 전달하기 위해 App.js를 다음과 같이 수정한다.

(...)

function App() {
  (...)
  return (
    <div className='App'>
      <Inputfield onCreate={onCreate}/>
      <section>
        <Todo todo={todo} />
      </section>
      <section>
        <Done todo={todo} />
      </section>
    <div>
  );
}
export default App;

map을 이용해 HTML 요소를 반복해 렌더링 한다. Todo 컴포넌트를 다음과 같이 수정한다. 또한 고유 id를 같도록 한다.

(...)
const Todo = ({todo}) => {
  return (
    <div className="Todo">
      <h1>Todo</h1>
      <div className="Todo_list">
        {todo.map( (it) => (
          <TodoItem key={it.id} {...it} />
        ))}
      </div>
    </div>

  )
}
export default Todo;

TodoItem.js도 다음과 같이 수정한다.

import "./TodoItem.css";

const TodoItem = ({id, content, isDone}) => {
  return (
    <div className="TodoItem">
      <div className="checkbox">
        <input onChange={isDone} type="checkbox" />
      </div>
      <div className="title">{content}</div>
      <div className="DeleteButton">
        <button>삭제</button>
      </div>
    </div>
  );
};
export default TodoItem;

onUpdate, onDelete

체크 박스를 클릭하면 isDone의 상태가 바뀌게 하는 onUpdate와 삭제 버튼을 누르면 해당 item이 삭제되는 onDelete 함수를 구현한다.
먼저 App.js를 다음과 같이 수정한다.

(...)

function App() {
  (...)
  const onUpdate = (targetId) => {
    setTodo(
      todo.map(
        (it) => {
          if (it.id === targetId) {
            return {
              ...it,
              isDone: !it.isDone,
            };
          } else {
            return it;
          }  
        } // it.id === targetId ? {...it, isDone: !it.Done } : it
      )
    );
  }; // 체크박스를 누르면 isDone: true

  const onDelete =(targetId) => {
    setTodo(todo.filter((it) => it.id !== targetId))
  } // 삭제를 누르면 Delete

  return (
    <div className='App'>
      <Inputfield onCreate={onCreate}/>
      <section>
        <Todo todo={todo} onUpdate={onUpdate} onDelete={onDelete} isDone={false}/>
      </section>
      <section>
        <Done todo={todo} onUpdate={onUpdate} onDelete={onDelete} isDone={true}/>
      </section>
    </div>
  );
}

export default App;

Todo.jsTodoItem.js를 다음과 같이 수정해 준다.

(...)
const Todo = ({todo, onUpdate, onDelete}) => {
  return (
    <div className="Todo">
      <h1>Todo</h1>
      <div className="Todo_list">
        {todo.map( (it) => (
          <TodoItem key={it.id} {...it} onUpdate={onUpdate} onDelete={onDelete} />
        ))}
      </div>
    </div>

  )
}
export default Todo;
import "./TodoItem.css";

const TodoItem = ({id, content, isDone, onUpdate, onDelete}) => {
  const onChangeCheckbox = () => {
    onUpdate(id);
  };

  const onClickDelete = () => {
    onDelete(id);
  };

  return (
    <div className="TodoItem">
      <div className="checkbox">
        <input onChange={onChangeCheckbox} checked={isDone} type="checkbox" />
      </div>
      <div className="title">{content}</div>
      <div className="DeleteButton">
        <button onClick={onClickDelete}>삭제</button>
      </div>
    </div>
  );
};
export default TodoItem;

여기서 TodoItem과 DoneItem의 차이점은 input 필드에 isDone을 !isDone으로 해주는 것이다.

웹 설명

현제 웹이서 input 필드에 content를 입력하면 Todo와 Done에 동시에 업데이트가 되며 Todo의 체크박스에는 빈칸이, Done의 체크박스에는 체크가 되어있는 것을 확인 할 수 있다. 이것은 map를 수정해 해결할 수 있을 것으로 보인다. 또한 Todo에서 체크 박스를 누르면 Done으로 갈 수 있게 구현 하는 것도 남았다. 삭제 버튼을 누르면 해당 item이 삭제 되는 것을 확인 할 수 있다.

댓글남기기