이프로젝트는, 일단 todolist의 확장판이라 할 수 있다.
기본적으로 기능을 붙여야한다 생각하였을때, todolist의 crud 가 선행되어야하고, 이후 다른 데이터바인딩이 필요하며, 그 이후 세부적인 다른 멤버를 클릭시 다른 멤버의 데이터가 보여야한다던지, 스케쥴러 작업과 같은 일을 해야한다 생각하였다.
그 중 먼저 todolist 를 보면, 1. TODO 하단의 + ADDTODO 로 Create 하기, 2. create 후 위에서 Read, 3. 연필아이콘 클릭시 Edit으로 Update, 4. 휴지통아이콘 클릭시 Delete, 5. 체크버튼 클릭시 todo update
//Todo.tsx
export default function Todo() {
const [isAdd, setIsAdd] = useState(false);
const newTodo = {
todoContent: todoContent,
author: 24,
};
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
setTodoContent(e.target.value);
};
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
await createTodo(newTodo);
setTodoContent("");
setIsAdd(false);
fetchData();
};
return (
~~~
{isAdd ? (
<form onSubmit={onSubmit} className="add-todo-container">
<input
type="text"
placeholder="할 일을 입력 후, Enter 를 누르세요"
onChange={onChange}
autoFocus={true}
/>
</form>
) : (
<label
className="add-todo-container"
onClick={() => {
setIsAdd(true);
}}
>
<Button variant="text">
<Icon.Plus size={20} color="#635fc7" />
</Button>
<Text
type="title"
size="lg"
color="primary"
style={{ paddingLeft: "10px" }}
>
ADD TODO
</Text>
</label>
)}
)
}
먼저, isAdd
라는 상태를 만들어놓고, 새로운 newTodo 라는 객체를 만들어준다.
여기서 author : 24
인 이유는 strapi특성상 author
에 해당하는 ID값이 들어가야하는데, 로그인한 유저의 ID값을 받아와야하지만, 아직 로그인 로직이 처리되지 않아서 임의의 user인 ID값 24를 넣어주었다. (나중에 로그인 구현 후 수정 예정)
onChange 함수는 말그대로 change될때의 e.target.value의 값을 set해주는 것!
onSubmit 함수는 submit 시 form의 기본동작인 새로고침을 막아주는 e.preventDefault()를 실행시키고, createTodo(newTodo)를 호출 한 뒤, setTodoContent의 값은 빈값으로 비어주고, 이제 작성중이 아니니까 setIsAdd(false), 그 이후 fetchData()를 한번 더 해주는 이유는? 새로고침을 해야 리렌더링이 되는 이슈가 있었는데, 이부분을 submit시 마지막에 fetchData를 해줌으로써, 전송과 즉시 데이터를 다시 패칭해주는 역할을 하게된다.
return 이하를 보자면, 조건부 렌더링으로 isAdd의 상태에 따라 컴포넌트를 다르게 보여준다.
isAdd 가 true인 경우는 새로운걸 작성할 상태이니까, input 태그를 보여주고,
isAdd가 false인 경우는 + ADD TODO의 버튼을 보여준다.
export default function Todo() {
const { data: todo, fetchData } = useAPI<TodoEntity>(fetchTodo, {
isFetch: true,
});
return (
<div className="todo-tontent-layout-wrapper">
{todo.map((t) => {
return (
<TodoContent
key={`${t.publishedAt}+1`}
content={t.todoContent!}
id={t.id!}
fetchData={fetchData}
/>
);
})}
</div>
)
}
<TodoContent/>
라는 컴포넌트로 return 해 줄 것이다.<TodoContent/>
interface Props {
content: string;
id: number;
fetchData: () => Promise<void>;
}
export default function TodoContent({ content, id, fetchData }: Props) {
const formRef = useRef(null);
const [isEdit, setIsEdit] = useState(false);
const [editTodoContent, setEditTodoContent] = useState(content);
const [isOpenModal, setIsOpenModal] = useState(false);
const clickModal = () => {
setIsOpenModal(true);
};
const closeModal = () => {
setIsOpenModal(false);
};
const editTodo: TodoParam = {
todoContent: editTodoContent,
author: 24, //TODO: 로그인한 사용자의 id 값을 넣어줘야함
};
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
setEditTodoContent(e.target.value);
};
const onSubmit = async (
e: React.FormEvent<HTMLFormElement>,
todo: TodoParam
) => {
e.preventDefault();
await updateTodo(todo, id);
if (todo === editTodo) {
setIsEdit(false);
fetchData();
} else {
setIsOpenModal(false);
}
};
const editFunc = () => {
setIsEdit(!isEdit);
if (isEdit && formRef) {
(formRef.current as any).dispatchEvent(
new Event("submit", { cancelable: true, bubbles: true })
);
}
};
return (
<>
<div className="todo-content-container">
<div className="todo-content-wrapper">
<button
onClick={() => {
clickModal();
}}
>
<Icon.CheckCircle size={18} color="white" />
</button>
{isEdit ? (
<form
ref={formRef}
onSubmit={(e: React.FormEvent<HTMLFormElement>) =>
onSubmit(e, editTodo)
}
>
<input type="text" defaultValue={content} onChange={onChange} />
</form>
) : (
<Text style={{ paddingLeft: "10px" }}>{content}</Text>
)}
</div>
<div className="todo-icon-wrapper">
<button onClick={() => editFunc()}>
<Icon.Edit2 size={18} color="white" />
</button>
<button
onClick={async () => {
await deleteTodo(id as number);
fetchData();
}}
>
<Icon.Trash size={18} color="white" />
</button>
</div>
</div>
{isOpenModal && (
<Modal
variant="certification"
onSubmit={onSubmit}
closeModal={closeModal}
/>
)}
</>
);
}
isEdit
의 상태를 만들어주었고, 편집된 내용의 editTodoContent
의 상태또한 만들었다.editTodo
, onChange
의 경우 위의 create부분과 동일하게 구현onSubmit
함수 에서 form
의 기본동작인 새로고침을 막아주는 e.preventDefault()
를 실행하고, updateTodo()
를 호출하는데, updateTodo
의 보내질 todo
와 editTodo
내용이 같으면, setIsEdit(false)
로 해주고, 아닐 경우는 왜 setIsOpenModal(false)를 해줬지...??? 😓 -> setIsOpenModal(false)를 안해주고 그냥 없애보니까, edit은 잘 동작하나, 만약 체크 버튼을 클릭하고 확인을 누를경우 onSubmit은 동작하지만 모달창이 꺼지지않는 문제가 있음...! 그래서 해주었구나!!formRef
의 값을 가져와서 사용하면되는데, 적용이안되어 구글링해보니, 리액트17 부터 이벤트에 cancelable과 bubbles properties 에 대한 true false 값을 적어줘야한다고 나와있다.