컴포넌트
- MemoEditor: 메모를 입력
- MemoBoard: 메모가 나타날 공간
- MemoItem: 각각의 메모
1. 메모 작성하기
MemoEditor.js
- State를 사용하여 입력된 값들을 저장
const [state, setState] = useState({
subject : "",
title : "",
content : "",
});
- 내용 입력 시 setState할 함수 생성
const handleChangeState = (e) => {
setState({
...state,
[e.target.name] : e.target.value
});
}
- 각각의 입력폼에 onChange={handleChangeState} 를 입력하여 해당 요소가 변화할 때 호출 될 함수를 만들었다.
- event를 매개변수로 받고 스프레드 연산자(...)을 사용하여 이전의 내용을 복사한다.
- [e.target.name] : e.target.value = 이벤트가 발생한 요소의 name을 [key] 로, 값을 value로 저장한다.
💡 useRef를 사용하여 focus 이동하기
* useRef ?
DOM 요소에 대한 참조를 생성하고 유지하기 위해 사용되는 Hook으로 함수형 컴포넌트에서도 DOM요소에 직접 접근할 수 있다.
// focus할 대상과 함수
const titleInput = useRef();
const contentInput = useRef();
// focus 실행
if(state.title.length == 0) {
titleInput.current.focus();
return
}
// focus 요소
<input ref={titleInput} />
이렇게 작성된 메모는 MemoList로 전달이 되어야 하는데
이와 같이 MemoEditor와 MemoList는 같은 레벨의 컴포넌트로 데이터를 주고받을 수 없다.
만약 데이터를 여러 컴포넌트에서 사용해야 한다면, 공통부모인 App.js에서 State를 관리하고 하위 컴포넌트로 데이터를 전달해야 한다.
- App.js에서 State를 정의하고 업데이트 하는 메소드 작성
- 하위 컴포넌트에 필요한 데이터를 props로 전달
- 하위 컴포넌트는 props를 통해 전달받은 데이터를 사용하고, 필요한 경우 eventHandler를 통해 App.js로 데이터를 전달한다.
App.js
// 전역적으로 관리할 data State
const [data, setData] = useState([]);
// memo ID
const dataId = useRef();
// useRef를 사용하는 이유?
// - DOM 요소에 직접 접근할 수 있기 때문에 이를 통해 DOM 요소의 속성이나 메서드에 접근하거나
// 직접 조작할 수 있다. 이는 컴포넌트 간에 데이터를 공유하는 데 사용되며
// .current를 사용하여 값을 읽거나 업데이트 할 수 있다.
/**
* MemoEditor에서 메모의 내용을 받아와 data에 set하는 함수
* @param {*} subject
* @param {*} title
* @param {*} content
*/
const onCreate = (subject, title, content) => {
// 작성일
const writtenDate = new Date().getTime();
const newItem = {
subject,
title,
content,
writtenDate,
id: dataId.current,
}
dataId.current += 1;
setData([newItem, ...data]);
}
// ...
// MemoEditor에서 onCreate 함수를 사용할 수 있도록 prop으로 전달
<MemoEditor onCreate={onCreate} />
// data를 Memboard에서 사용할 수 있도록 prop으로 전달
<MemoBoard memoBoard={data} />
MemoEdiotr.js
// onCreate 함수를 App.js에서 받아옴
const MemoEditor = ({onCreate}) => {
//.. 중략
// '작성'버튼을 누르면 실행될 함수에 onCreate를 사용하여 매개변수를 통해 App.js로 데이터 전달
const handleSubmit = () => {
onCreate(state.subject, state.title, state.content);
}
}
MemoBoard.js
// App.js에서 prop로 전달한 data를 받음 (memoBoard = {data} -> memoBoard는 prop이름)
const MemoBoard = ({memoBoard}) => {
{memoBoard.map((it)=>(
<MemoItem key={it.id} {...it}/>
))}
}
// map함수를 통해 순회하며 각 it은 memoBoard 배열의 원소 객체를 나타내며,
// 각각의 Item에 key로 id를 지정하고, {...it}를 통해 객체의 모든 속성들을 개별적인 props로 전달한다.
// 이로써 MemoItem 컴포넌트는 memoBoard의 각 원소에 대한 정보를 개별적으로 받아와 사용할 수 있게 된다.
MemoItem.js
const MemoItem = ({id, subject, title, content, writtenDate}) => {}
// memoBoard에서 전달한 개별 속성들을 매개변수로 받아와서 사용하면 된다.
2. 메모 삭제 및 수정
(삭제)
삭제 버튼을 클릭하면 App.js의 State가 변해야 하기 때문에 App.js에서 삭제를 하는 함수를 만들어서 하위 컴포넌트로 전달해야 한다.
ex) data: [{item1}, {item2}, {item3}] -- (item2삭제 시) --> [{item1}, {item3}]
App.js
const onDelete = (targetId) => {
// fitler 함수를 사용하여 data 중에서 id가 targetId와 같지 않은 요소들로 이루어진 새로운 배열 생성
const newMemoBoard = data.filter((it)=>it.id !== targetId);
setData(newMemoBoard);
}
이렇게 만든 onDelete 함수를 하위 컴포넌트로 전달한다.
// App.js
<MemoBoard memoBoard={data} onDelete={onDelete}/>
// MemoBoard.js * 이 컴포넌트에서는 사용되지 않아도 MemoItem으로 전달하기 위해 추가해야함)
const MemoBoard = ({onDelete, memoBoard}) => {
<MemoItem key={it.id} {...it} onDelete={onDelete}/>
}
// MemoItem.js
const MemoItem = ({onDelete, id, subject, title, content, writtenDate}) => {
const handleDelete = () => {
if (window.confirm(`${id}번째 메모를 삭제하시겠습니까?`)) onDelete(id);
}
return <div>
// ...중략
<button onClick={handleDelete}>×</button>
</div>
}
(수정)
삭제와 마찬가지로 수정이 완료되면 data의 값 또한 변화해야 하기 때문에 App.js에서 상태를 관리하며 이를 하위 컴포넌트로 전달한다.
App.js
const onEdit = (targetId, newContent) => {
// id가 tragetId와 일치한다면 해당 요소는 수정 대상으로 content만 newContent로 변경,
// 일치하지 않으면 원래 요소 반환
setData(
data.map((it) => it.id === targetId ? {...it, content:newContent} : it)
);
}
하위 컴포넌트로 전달
// App.js
<MemoBoard memoBoard={data} onDelete={onDelete} onEdit={onEdit}/>
// MemoBoard.js * 이 컴포넌트에서는 사용되지 않아도 MemoItem으로 전달하기 위해 추가해야함)
const MemoBoard = ({onEdit, onDelete, memoBoard}) => {
<MemoItem key={it.id} {...it} onDelete={onDelete} onEdit={onEdit}/>
}
MemoItem.js
const MemoItem = ({onEdit, onDelete, id, subject, title, content, writtenDate}) => {
// 현재 내용이 수정중인지 아닌지 값을 보관할 State
const [isEdit, setIsEdit] = useState(false);
// 호출되면 기존 isEdit이 가지고 있던 값을 반대로 set (버튼 클릭 시 호출)
const toggleIsEdit = () => setIsEdit(!isEdit);
// 수정된 내용의 State (기초값으로 원래의 content 설정)
const [editedContent, setEditedContent] = useState(content);
// 수정 취소 버튼을 누르면 수정중이던 내용이 초기화되는 함수
const handleCancledEdit = () => {
setIsEdit(false);
setEditedContent(content);
}
// 수정 완료 시 App.js로 data를 보내는 event
const handleEdit = () =>{
if(window.confirm(`'${title}' 메모를 수정하시겠습니까?`)) {
onEdit(id, editedContent);
toggleIsEdit();
}
}
return <div>
// .. 중략
// 메모 내용
<div>
{/* isEdit이 true일 경우 수정 form이 나옴 */}
{isEdit ?
<>
<textarea
className="editContent"
value={editedContent}
onChange={(e) => setEditedContent(e.target.value)}
/>
</>
:
<>{content}</>}
</div>
// 버튼
<div>
{/* isEdit이 ture일 경우 수정취소/수정완료 버튼 */}
{isEdit ?
<>
<button onClick={handleCancledEdit}>수정 취소</button>
<button onClick={handleEdit}>수정 완료</button>
</>
:
<>
<button onClick={toggleIsEdit}>수정</button>
</>
}
</div>
</div>
}
💡 메모 주제별로 배경색 지정하기
MemoItem.js
// 메모 배경 색 설정
const memoBackColor = () => {
if(subject === "기타") return "#ffff86"
if(subject === "쇼핑") return "skyblue"
if(subject === "할 일") return "pink"
if(subject === "공부") return "rgb(207, 238, 160)"
}
return <div className="memoItem" style={{ backgroundColor: memoBackColor() }}> ... </div>
처음에는 App.js에 만들어둔 onCreate 함수에서 위의 if문을 썼었는데 그러면 가장 최근에 추가된 메모는 색이 지정이 안되었다.
색이 지정되는 타이밍이 문제라 생각하여 MemoItem이 생성될 때 <div class="memoItem"> .. </div> 여기에 인라인으로 스타일을 주어 추가되게 하는 게 좋을 거 같아 위처럼 작성하였더니 해결되었다....
완성!
'JS > React Project' 카테고리의 다른 글
🏃♀️ 목표 다이어리 - 글 작성 / 수정 (0) | 2023.08.07 |
---|---|
🏃♀️ 목표 다이어리 만들기 - 홈화면 (0) | 2023.08.01 |
🏃♀️ 목표 다이어리 만들기 - 기획 및 기초 세팅 (0) | 2023.07.28 |
📝 메모앱 업그레이드 (0) | 2023.07.25 |
📝 메모앱 최적화하기 (0) | 2023.07.22 |