데이터 페이지에서 구현할 기능은 다음과 같다.
백엔드 서비스로 firebase를 사용했다.
토이프로젝트에서 대중적으로 사용하는 개발 플랫폼이기도 하고
기존에 인프런 프론트엔드 강의를 들었을 때 써본 내용을 복습하기 위해서 선택했다.
💬 요즘 supabase관련 글도 많이 보여서 추후에는 해당 플랫폼으로도 구현해볼 것
데이터 입력
const onClickAddArticle = async () => {
const list = collection(db, 'list'); // 'list'컬렉션 생성(지정)
const listRef = await addDoc(list, { // 'list'안에 문서 생성
id: '',
title: inputs.title,
link: inputs.link,
});
await updateDoc(doc(db, 'list', listRef.id), { id: listRef.id }); // 자동생성된 id를 데이터의 id 필드에 업데이트
setInputs({ id: '', title: '', link: '' });
};
firebase에서 문서를 생성하는 방법은 두가지로 setDoc()
와 addDoc()
이 있다.
setDoc()
은 문서의 id를 직접 지정할 수 있다.addDoc()
은 임의로 문자열의 id가 생성된다.여기서는 임의의 아이디를 만들어서 문서의 필드에 넣어놓고 추후 삭제 등의 기능에 활용하기 위해 addDoc()
을 사용했다.
💬 이렇게 업데이트해서 필드에 넣는 방식이 아니라 문서가 생성될 때 자동으로 id필드를 만들어놓을 순 없을까? 이런 기능이 firebase내에 있을 것 같은데 찾아볼 것.
데이터 조회
useEffect(() => {
const list = collection(getFirestore(firebaseApp), 'list');
const newListsData = onSnapshot(list, (snapshot) => {
const datas = snapshot.docs.map((doc) => doc.data());
console.log(datas);
setListsData(datas);
});
return () => {
newListsData();
};
}, []);
페이지가 렌더링 된 후 바로 데이터리스트가 보이도록 하기 위해 useEffect()
안에 해당 함수를 넣었다.
firebase에서 전체 문서를 조회하는 방법에는 getDocs()
와 onSnapshot()
이 있는데
getDocs()
는 호출시 데이터를 한번 가져오고onSnapshot()
은 실시간으로 데이터의 업데이트를 감지하고 데이터가 업데이트 될 때마다 새로 가져온다.여기서는 사용자가 새 데이터를 추가했을 때 바로 하단의 리스트가 업데이트 되어야하므로 onSnapshot()
을 사용했다.
🤬 에러일지 useEffect 무한렌더링
const [listsData, setListsData] = useState<any[]>([]); ... useEffect(() => { const list = collection(getFirestore(firebaseApp), 'list'); const fetchLists = async () => { const result = await getDocs(list); const datas = result.docs.map((el) => el.data()); setListsData(datas); }; fetchLists(); }, [listData]);
처음에는
getDocs()
를 사용한 뒤useEffect()
에서listData
가 변경될 때 리렌더링을 하면 되지 않을까 했는데 무한렌더링의 늪에 빠졌다.
이유인즉슨..
useEffect()
안에서setState()
로 변경되는state
를useEffect()
의 변경조건으로 넣으면([state]
) 아래 두 과정이 무한히 반복되는 것과 같기 때문이다..🤯
😇onSnapshot()
기능 사용으로 데이터 변경시 자동 업데이트 되도록 변경 완료
📄 presenter.tsx
<s.IsRead
checked={el.isRead}
onChange={props.onChangeRead}
id={el.id}
>
Done
</s.IsRead>
📄 container.tsx
const onChangeRead = async (e: CheckboxChangeEvent) => {
const data = listsData.find((el) => el.id === e.target.id);
if (data) {
const updatedIsRead = !data.isRead;
await updateDoc(doc(db, 'list', data.id), { isRead: updatedIsRead });
}
};
checkbox
(isRead컴포넌트
) 클릭시 onChangeRead
함수가 실행된다. 작동방식은 아래와 같다.
updateDoc()
으로 id필드
에 넣어놓은 id값과 일치하는 문서를 find()
메소드로 특정한다.isRead필드
를 이전과 반대로 변경한다.updateDoc()
을 활용해 db의 문서 정보로 업데이트한다.📄 presenter.tsx
{props.listsData.map((el, index) => (
<s.List key={el.id}>
<s.Title href={el.link} target='_blank'>
{el.title}
</s.Title>
<s.ListMenu>
<s.IsRead>Done</s.IsRead>
<DeleteOutlined
onClick={props.onClickDeleteArticle} // 클릭시 delete 실행
id={el.id}
/>
</s.ListMenu>
</s.List>
))}
📄 container.tsx
const onClickDeleteArticle = async (e: MouseEvent<HTMLSpanElement>) => {
await deleteDoc(doc(db, 'list', e.currentTarget.id)); // id 받아와서 해당 문서 삭제
};
icon
클릭시 onClickDeleteArticle
함수가 실행된다. 작동방식은 아래와 같다.
updateDoc()
으로 id필드
에 넣어놓은 id값으로 문서를 특정한다.deleteDoc()
메소드를 이용해 해당 문서를 삭제한다.<const router = useRouter();
...
const onClickMoveToHome = () => {
router.push('/');
};
useRouter()
와 push()
메소드를 사용해 아이콘 클릭시 main 페이지로 이동하도록 구현했다.
역시 직접 만들어봐야 머릿속에 개념이 잡히는 것 같다.
인프런 프론트엔드 강의로 익혔던 개념들을 하나하나 다시 활용해보면서 복습하게 된다.
이제 메인페이지도 후딱 만들어봐야지.
나의 작고 소중한 토이프로젝트..