
react는 단방향 흐름을 가지고 있습니다. 이 말은 부모페이지가 자식에게 데이터를 보내주는게 가능하나 자식이 부모에게 전달하는 것은 불가능 합니다 예제를 확인해보죠
const dummyList = [
{
id: 1,
author: "Bob",
content: "Goood",
emotion: 5,
created_date: new Date().getTime()
},
{
id: 2,
author: "Bob",
content: "Goood",
emotion: 5,
created_date: new Date().getTime()
},
{
id: 3,
author: "Bob",
content: "Goood",
emotion: 5,
created_date: new Date().getTime()
}
];
const App = () => {
return (
<div className="App">
<DiaryEditor />
<DiaryList diaryList={dummyList} /> //여기가 포인트
</div>
);
};
export default App;
조금 react를 찍먹해보시셨으면 느끼셨겠지만 diaryList={dummyList}로 App.js에서 컴포넌트는 보내지만 받는 방법은 없습니다. 이렇듯 재사용이 필요한 컴포넌트를 수정할 수 있게 하는 방법이 앞서 배웠던 useState를 이용하는 겁니다.
const App = () => {
const [data, setData] = useState([]);
const dataId = useRef(0);
const onCreate = (author, content, emotion) => {
const created_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
created_date,
id: dataId.current
};
dataId.current += 1;
setData([newItem, ...data]);
};
return (
<div className="App">
//DiaryEditor와 List는 App.js의 하단에 있는 레밸계층이 같습니다.
//Editor에서는 onCreate함수를 넘겨주어 setData를 하게만듭니다.
//setData에 의해 수정된 data 리스트는 DiaryList로 re-render 됩니다.
<DiaryEditor onCreate={onCreate} />
<DiaryList diaryList={data} />
</div>
);
};
자, 그럼 위에서 만든 소스에 DiaryEditor.DiaryList를 이어서 만들어봅니다.
이전 포스트의 내용에서 소스를 추가하시면 됩니다.
onCreate함수 프로퍼티만 넘겨받아 이를 사용하는, handler만 바꿔주면 되겠습니다.
const DiaryEditor = ({ onCreate }) => { // 이부분 prop 추가
//생략...
const handleSubmit = () => { // handleSubmit를 수정하면 될거같습니다.
if (state.author.length < 1) {
authorInput.current.focus();
return;
}
if (state.content.length < 5) {
contentInput.current.focus();
return;
}
onCreate(state.author, state.content, state.emotion); // 이부분 추가
alert("저장 성공");
setState({
author: "",
content: "",
emotion: 1
});
};
//생략...
그럼 위에 App.js에서 작성한 onCreate가 실행이 됩니다.
const App = () => {
const [data, setData] = useState([]);
//중략...
const onCreate = (author, content, emotion) => {
//중략...
setData([newItem, ...data]);
//중략...
};
//중략...
};
우리가 지정한 data가 setData에 의해 새로운값을 앞에 배치하고, 뒤에 배열을 이어 붙히게 됩니다.
DiaryItem.js는 App.js에서 diaryList받아옵니다. 이는 곧 data를 의미하죠
import DiaryItem from "./DiaryItem";
const DiaryList = ({ diaryList }) => {
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it) => (
<DiaryItem key={`diaryitem_${it.id}`} {...it} />
//map으로 list를 불러오게 되면 key라는게 필요합니다. 임의로 count하여 넣어주었습니다.
//값이 없어도 렌더링은 됩니다만, 콘솔에 에러가 찍힙니다.
))}
</div>
</div>
);
};
DiaryList.defaultProps = {
diaryList: [] // 디폴트값은 빈 배열을 넣어줍니다.
};
export default DiaryList;
이렇게해서 가져온 List는 또다시 DiaryItem.js으로 묶어서 계층별로 관리하면 됩니다.
const DiaryItem = ({ id, author, content, emotion, created_date }) => {
return (
<div className="DiaryItem">
<div className="info">
<span className="author_info">
| 작성자 : {author} | 감정점수 : {emotion} |
</span>
<br />
<span className="date">{new Date(created_date).toLocaleString()}</span>
</div>
<div className="content">{content}</div>
</div>
);
};
export default DiaryItem;
삭제를 합니다. 삭제는 앞에서 사용하였던 key 프로퍼티를 이용합니다. 마찬가지로 부모쪽의 데이터를 수정해야하므로, 함수 컴포넌트도 App.js에 작업을 합니다.
onDelete 함수는 아래와 같이 filter를 이용하여 List를 재가공합니다.
//App.js에 onCreate 아래에 추가합니다.
const onDelete = (targetId) => {
const newDiaryList = data.filter(
(it) => it.id !== targetId
);
setData(newDiaryList);
};
//return 쪽에 onDelete를 추가합니다.
return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
<DiaryList diaryList={data} onDelete={onDelete} />
</div>
);
마찬가지로 DiaryList는 DiaryItem쪽으로 컴포넌트를 최종적으로 전달해주고 Item에 각각 delete버튼을 생성해서 사용하면 됩니다.
const DiaryList = ({ onDelete, diaryList }) => { // onDelete 추가
return (
<div className="DiaryList">
<h2>일기 리스트</h2>
<h4>{diaryList.length}개의 일기가 있습니다.</h4>
<div>
{diaryList.map((it) => (
<DiaryItem key={it.id} {...it} onDelete={onDelete} /> // onDelete 추가
))}
</div>
</div>
);
};
이제 아이템에도 실행할 수 있는 함수를 적용해주면 됩니다.
const handleClickRemove = () => {
if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
onDelete(id);
}
};
//생략...
return (
//적절한 부분에 버튼을 만들어서 제공하면 됩니다.
<button onClick={handleClickRemove}>
삭제하기
</button>
)
앞서 등록과 삭제를 설명하고 수정을 진행합니다. 수정이 좀까다로와서 마지막에 두었습니다.
수정은 기존에 작성한 원본을 input에 담아줄 수 있어야하며 있어야하며, 현상황에따라 게시물이 수정 상태인지, 그냥 게시 상태인지를 구분 할 줄도 알아야 합니다.
앞서 말씀드렸다시피 input에 기존글을 넣어서 보여주고 onChange시 새로운 입력 값을 받아줘야 합니다. 해당 컴포넌트는 DiaryItem에 작성하면됩니다.
//새로운 content도 검증이 필요하고 focus도 필요하겠죠? useRef를 만듭니다.
const localContentInput = useRef();
//새로운 content용 useState도 만듭니다. 이때 초기값은 항상 content, 즉 원글이 항상 딸려옵니다.
const [localContent, setLocalContent] = useState(content);
// 현재 상태가 게시상태인지, 수정상태인지를 boolean으로 확인 합니다.
const [isEdit, setIsEdit] = useState(false);
// 토글 버튼입니다. 한번씩 실행할때마다 반전되는 효과를 가져옵니다.
const toggleIsEdit = () => setIsEdit(!isEdit);
그리고 함수를 정의 합니다.
const handleQuitEdit = () => { //수정취소시
setIsEdit(false);
setLocalContent(content);
};
const handleEdit = () => {// 수정완료 버튼 클릭시
if (localContent.length < 5) {
localContentInput.current.focus();
return;
}
if (window.confirm(`${id}번 째 일기를 수정하시겠습니까?`)) {
onEdit(id, localContent);
toggleIsEdit();
}
};
이제 렌더링될 페이지만 만들면 됩니다. 3항식을 이용해서 상태에따라 화면에 보이는 구조를 바꿔서 보여줍니다.
return (
<div className="DiaryItem">
<div className="info">
<span className="author_info">
작성자 : {author} | 감정 : {emotion}
</span>
<br />
<span className="date">
{new Date(created_date).toLocaleDateString()}
</span>
</div>
<div className="content">
{isEdit ? ( //참일시 수정을 위한 input tag를 생성합니다.
<textarea
ref={localContentInput}
value={localContent}
onChange={(e) => setLocalContent(e.target.value)}
/>
) : ( // 아니면 그냥 글자만 보여줍시다.
content
)}
</div>
{isEdit ? (
<>
<button onClick={handleQuitEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button>
</>
) : (
<>
<button onClick={handleClickRemove}>삭제하기</button>
<button onClick={toggleIsEdit}>수정하기</button>
</>
)}
</div>
);
};