해결된 질문
작성
·
48
0
글 수정기능을 추가하고 useReducer로 바꿔서 했는데
글을 추가해도 새로운 내용이 나타나지 않고, 마지막 요소가 다시 추가되었습니다. [{content:"To1"}, {content:"To1"}]
확인해보니 컴포넌트에서 받은 props를 useState초기화값으로 넣으면 바뀌지 않는 사실을 알게되었습니다.
하지만 이부분을 useEffect형식으로 바꿔 적어주니 새로운 내용으로 바뀌는 것을 확인했습니다.
// props content를 useState 초기화값으로 적용
const TodoItem = ({ content, id, isDone, date, onUpdate, onDelete }) => {
const [upContent, setUpcontent] = useState(content);
...
}
////////////////////////////////////////////
// useEffect 적용
const TodoItem = ({ content, id, isDone, date, onUpdate, onDelete }) => {
const [upContent, setUpcontent] = useState("");
useEffect(() => {
if (content) {
setUpcontent(content);
}
}, [content]);
...
}
useReducer를 적용하지 않을때 props를 useState초기화값을 넣어도 잘 구동되었습니다.
// props content를 useState 초기화값으로 적용
const TodoItem = ({ content, id, isDone, date, onUpdate, onDelete }) => {
const [upContent, setUpcontent] = useState(content);
...
}
이것이 리액트 라이프 사이클 때문에 이러한 현상이 발생한것인가요?
답변 2
0
useReducer로 했는데 오류가 나지 않아 맞는 코드라고 생각했습니다.
전체 코드 올려 드렸습니다.
useState 사용 시 깃허브 코드입니다.
useReducer 사용 시 깃허브 코드입니다.
type별로 체크박스와 content update를 나눠서 선언을 해야 하는군요
바뀐 코드로 실행해보았는데
TodoItem.jsx의
useEffect부분을 주석처리해도 실행되는 이유가 무엇인가요?
useEffect(() => { setLocalContent(content); }, [content]);
해당 코드의 역할은 App 컴포넌트의 content State가 변화할 경우 localContent에 동기화 시키는 역할인데, 현재로써는 localContent의 값이 먼저 변화한 이후에 content의 값이 변화하기 때문에 없어도 문제없이 동작합니다.
0
안녕하세요 이정환입니다.
먼저 좀 더 질문을 구체화 해 주실 수 있을까요? TodoItem 컴포넌트 하나만으로는 말씀하신 상황을 구체적으로 파악하기 어려울 것 같습니다. 강의와 코드도 조금 다른 것 같구요!
대략적으로만 파악하자면 useState의 초기값으로 Props로 받은 content를 고정해두었을 때와 useEffect를 통해 content의 값이 변화할 때 setUpContent를 호출하는 것과의 차이를 물어보신 것 같은데요 useState의 초기값은 컴포넌트의 리렌더링에 반응하지 않습니다.
즉 content로 제공되는 Props의 값이 변화한다고 해서 초기값이 다시 설정되거나 하는건 아니라는거죠 그렇기 때문에 content Props의 값이 변화할 것으로 예상된다면 useEffect를 사용하는게 더 좋은 방법이 될 수 있을 것 같습니다. 물론 이는 제한된 내용을 기반으로 답변된 내용이라 정확하지 않을 수 있습니다.
todolist에서 content 수정기능을 추가하였습니다. 그래서 코드가 다른점 이해해주세요.
section8 강의처럼 useSate로만 사용할때 아래의 코드를 사용하면 제대로 출력됩니다.
App.jsx
function App() {
const [todos, setTodos] = useState(dummyData);
const idRef = useRef(3);
const onCreate = (content) => {
const newTodo = {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
};
setTodos([newTodo, ...todos]);
};
const onUpdate = (targetId, upContent) => {
if (!isNaN(targetId)) {
setTodos(todos.map((todo) => (todo.id === targetId ? { ...todo, isDone: !todo.isDone } : todo)));
} else {
setTodos(todos.map((todo) => (todo.id === targetId ? { ...todo, content: upContent } : todo)));
}
};
const onDelete = (targetId) => {
setTodos(todos.filter((todo) => todo.id !== targetId));
};
TodoItem.jsx
const TodoItem = ({ content, id, isDone, date, onUpdate, onDelete }) => {
const editInputRef = useRef(null);
const [isEdit, setIsEdit] = useState();
const [upContent, setUpcontent] = useState(content);
// content를 useState 초기화 선언
const onChangeCheckbox = () => {
onUpdate(id);
};
const onClickDeleteButton = () => {
onDelete(id);
};
const onClickUpdateButton = () => {
onUpdate("_", upContent); // content text updated
setIsEdit(!isEdit);
};
const onChangeUpdate = (e) => {
setUpcontent(e.target.value);
};
return (
<div className="TodoItem">
<input onChange={onChangeCheckbox} type="checkbox" readOnly checked={isDone} />
{isEdit ? (
<input type="text" value={upContent} ref={editInputRef} onChange={onChangeUpdate} />
) : (
<div className="content">{upContent}</div>
)}
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button onClick={onClickDeleteButton}>삭제</button>
<button onClick={onClickUpdateButton}>수정</button>
</div>
);
};
export default TodoItem;
하지만 useReducer 사용 시 todos 새로운 content가 추가되지 않고 todos의 끝 항목이 계속 복사되고 있습니다.
App.jsx
import Editor from "./components/Editor";
import Header from "./components/Header";
import List from "./components/List";
import "./App.css";
import { useState, useRef } from "react";
import { useReducer } from "react";
const dummyData = [
{ id: 0, isDone: true, content: "Todo1", date: new Date().getTime() },
{ id: 1, isDone: false, content: "Todo2", date: new Date().getTime() },
{ id: 2, isDone: false, content: "Todo3", date: new Date().getTime() },
];
function reducer(state, action) {
switch (action.type) {
case "CREATE":
console.log([action.data, ...state]);
return [action.data, ...state];
case "UPDATE":
return !isNaN(action.targetId)
? state.map((todo) => (todo.id === action.targetId ? { ...todo, isDone: !todo.isDone } : todo))
: state.map((todo) => (todo.id === action.targetId ? { ...todo, content: action.upContent } : todo));
case "DELETE":
return state.filter((todo) => todo.id !== action.targetId);
default:
return state;
}
}
function App() {
const [todos, dispatch] = useReducer(reducer, dummyData);
const idRef = useRef(3);
const onCreate = (content) => {
dispatch({
type: "CREATE",
data: { id: idRef.current++, isDone: false, content: content, date: new Date().getTime() },
});
};
const onUpdate = (targetId, upContent) => {
console.log(targetId);
dispatch({
type: "UPDATE",
targetId: targetId,
upContent: upContent,
});
};
const onDelete = (targetId) => {
dispatch({
type: "DELETE",
targetId: targetId,
});
};
return (
<div className="App">
<Header />
<Editor onCreate={onCreate} />
<List todos={todos} onUpdate={onUpdate} onDelete={onDelete} />
</div>
);
}
export default App;
TodoItem.jsx
import React, { useEffect, useRef, useState } from "react";
import "./TodoItem.css";
const TodoItem = ({ content, id, isDone, date, onUpdate, onDelete }) => {
const editInputRef = useRef(null);
const [isEdit, setIsEdit] = useState();
const [upContent, setUpcontent] = useState("");
// const [upContent, setUpcontent] = useState(content); -> 이렇게 사용하고 useEffect를 사용하지 않으면 새로운 content가 추가되지 않고 todos의 끝 항목이 계속 복사됩니다.
useEffect(() => {
if (content) {
setUpcontent(content);
}
}, [content]);
const onChangeCheckbox = () => {
onUpdate(id);
};
const onClickDeleteButton = () => {
onDelete(id);
};
const onClickUpdateButton = () => {
if (!isDone) {
onUpdate("_", upContent); // content text updated
setIsEdit(!isEdit);
}
};
const onChangeUpdate = (e) => {
setUpcontent(e.target.value);
};
return (
<div className="TodoItem">
<input onChange={onChangeCheckbox} type="checkbox" readOnly checked={isDone} />
{isEdit ? (
<input type="text" value={upContent} ref={editInputRef} onChange={onChangeUpdate} />
) : (
<div className={!isDone ? "content" : "cancelContent"}>{upContent}</div>
)}
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button onClick={onClickDeleteButton}>삭제</button>
<button onClick={onClickUpdateButton}>수정</button>
</div>
);
};
export default TodoItem;
안녕하세요 이정환입니다.
아하 수정 기능을 추가하고 싶으신거군요 그러나 이렇게 작성하시면 오류가 발생합니다.
TodoItem 컴포넌트에서 onUpdate 함수를 호출하면서 id 값을 전달하지 않는 경우 App 컴포넌트에서는 어떤 데이터를 수정해야 할지 알 수 없습니다.
예를 들어 3번 일기에서 content를 "aaa"로 수정한 다음 수정 버튼을 클릭하면 App 컴포넌트의 onUpdate에서는 몇번 일기가 수정되어야 하는지 어떻게 알 수 있나요?
또 지금처럼 하나의 Action Type에 두개 이상의 동작을 정의하는것은 바람직한 방식이 아닙니다. 따라서 지금과 같은 방식 보다는 useReducer에 Action Type을 하나 추가해 작업하시는게 좋아보입니다. UPDATE_CONTENT 로 추가하면 괜찮을 것 같습니다.
추가로 질문 가이드라인 확인을 부탁드립니다😃
프로젝트에서 발생한 이슈의 정확한 원인을 파악하려면 전체 코드가 필요합니다. 따라서 현재로써는 더 구체적인 답변을 드릴 수 없어 아쉽습니다 ... ㅠㅠ
여기서의 전체 코드는 파일 자체를 말씀드리는 것으로 깃허브 OR 코드 샌드박스 등의 수단을 이용해 링크로 전달해주시면 구체적으로 살펴볼 수 있습니다.
원하시는 기능을 직접 구현해봤습니다. 강의 수강 이후 제가 작성한 코드를 살펴보시면 동작 원리를 충분히 이해하실 수 있을겁니다.
아래 기재해두지 않은 파일의 내용은 변동 없습니다.
App.js
List.jsx
TodoItem.jsx