나의 개발 기록지
개인 포트폴리오 고민 상담소 #3 본문
오늘의 목표는 상세페이지와 글 작성 기능을 끝내버릴 것
상세페이지 부터 부셔버리기
저번 시간에 만들어 둔 글 리스트에서 리스트를 클릭하면 상세페이지로 넘어가게 끔 만들어주어야한다.
라우터에 페이지를 추가해준 뒤 경로까지 지정해준 다음 리스트아이템에 useNavigate를 사용해줄 것이다.
Link태그를 사용해도되지만 useNavigate를 사용하기로했다.
DetailPage.tsx를 만들어 준 뒤 (아직은 내용물이 아무것도 없으니 생략)
routes.tsx에 추가해줬다.
익명 게시판과 비밀 게시판은 똑같은 느낌으로 만들거라 같은 디테일페이지를 사용하기로 했다.
그리고 useNavigate로 이동할 경로를 잡아주는게 중요한데 하나의 컴포넌트에서 두개의 페이지 상세페이지를 관리하려다보니 어떤식으로 해주어야 /anonymous/id값 , /secret/id값 으로 이동할 수 있을지 고민을 잠깐 했었는데 생각보다 금방 해결했다.
파이어베이스에서 데이터베이스에 저장할 때 제목 본문 유저 시간 말고 카테고리도 추가해주었다.
그리고 그 카테고리를 사용하여 경로를 잡아줄 때 같이 사용해주기로 했다.
secret일땐 secret/ anonymous일땐 anonymous/ 으로 잡아줄 수 있게 그러고 id값은 파이어베이스에서 자체적으로 잡아주는 id값을 사용하기로 했다.
- 여기서 잠깐 id값을 사용하기위해 데이터를 가져오는 useFetchData 커스텀 훅에 약간의 코드 변경이 있었는데 그것만 기록하고 넘어가자
querySnapshot.forEach((doc)=>{
console.log(doc.id)
const docData = doc.data();
const docPushData = {id: doc.id, ...docData};
fetchedData.push(docPushData)
setData(fetchedData)
데이터를 넣어주는 부분에서 한번 더 가공을 하고 추가를 해줬다. 데이터 안에 id값이 존재하지않았기에 이런식으로 추가해줬다.
이제 상세페이지 지정은 끝났으니 테스트를 위해 리스트를 클릭해서 이동을 해보면
상세 페이지로 잘 넘어가며 뒤에 id값인 test_Anonymous도 잘 나타난다. (지금 id값이 test_Anonymous인 이유는 파이어베이스에서 임시로 내가 만들어 둔 데이터 이름이기 때문이다.)
이제 상세 페이지로 잘 넘어오니 그 페이지의 데이터를 받아오기만하면 상세 페이지도 완성인 셈이다.
이 부분에서는 파이어베이스에서 컬렉션에서 같은 아이디값을 찾아오면 끝이라 파이어베이스 문서를 좀 보면 금방 해낼 것 같다.
- 해결 하긴 했으나 파이어베이스 문서로 해결하진 못했다,, GPT의 도움으로 해결을 했다.
하나의 데이터만 가져올 방법을 못 찾겠어서 GPT에게 물어봤더니 금세 해결이 됐다.
import { doc, getDoc } from "firebase/firestore/lite";
import { db } from "../api/firebase";
import { useEffectOnce } from "usehooks-ts";
import { useState } from "react";
import { ListItemType } from "../types/types";
type useFetchDataProps ={
collectionName: string;
dataId?: string;
}
export default function useFetchDetailData({collectionName,dataId}: useFetchDataProps) {
const [data,setData] = useState<ListItemType>()
useEffectOnce(()=>{
const fetchData = async () => {
try{
if(dataId){
const docRef = doc(db,collectionName,dataId);
const docSnapshot: any = await getDoc(docRef);
if(docSnapshot.exists()){
const docData = docSnapshot.data();
const fetchedData = docData;
setData(fetchedData)
}
}
}catch(error){
console.error(`${error}에러뜨는중`)
}
}
fetchData()
})
return data
}
일단 코드는 위와 같이 생겼고 앞에서 만든 리스트 데이터 가져오는 것과의 차이점은 docs, doc의 차이인 듯 하다.
db(db,컬렉션이름,가져올데이터id)로 사용하니 원하는 데이터만 가져올 수 있었고 상세페이지에서는 id값을 굳이 사용하지 않아도 될 듯 해서 따로 추가해주지는 않았다.
이제 사용해야 할 상세페이지에서 커스텀 훅을 가져다 사용하면 끝인데
import { useParams } from "react-router-dom";
import PageContainer from "../../styles/ContainerStyle";
import useFetchDetailData from "../../Hooks/useFetchDetailData";
export default function DetailPage(){
const { id } = useParams()
const detailData = useFetchDetailData({collectionName:'anonymous', dataId: id?.toString()})
console.log(detailData)
if (!detailData) {
return (
<PageContainer>
<p>Loading...</p>
</PageContainer>
);
}
return (
<PageContainer>
<h1>{detailData.title}</h1>
<p>{detailData.content}</p>
</PageContainer>
);
}
일단 데이터는 잘 받아오고 화면에 출력도 잘 되고있다. 근데 다만 이제 문제는 이 상세페이지 하나로 익명페이지 비밀페이지를 다 사용하려면 collectionName에 카테고리를 받아와서 사용을 해야한다는 점... 이것만 해결하면 완성일 듯 하다.
이걸 해결하기 위해서는 id값을 받아오는 params를 사용했다.
{path: '/:category/:id', element: <DetailPage/>},
routes.tsx에서 상세페이지를 위와 같이 잡아주고
import { useParams } from "react-router-dom";
import PageContainer from "../../styles/ContainerStyle";
import useFetchDetailData from "../../Hooks/useFetchDetailData";
export default function DetailPage(){
const { id,category } = useParams()
const detailData = useFetchDetailData({collectionName: category, dataId: id?.toString()})
if (!detailData) {
return (
<PageContainer>
<p>Loading...</p>
</PageContainer>
);
}
return (
<PageContainer>
<h1>{detailData.title}</h1>
<p>{detailData.content}</p>
</PageContainer>
);
}
디테일 페이지에서 params로 category도 받아와 사용해주면 끝이었다.
다만 여기서 collectionName에 타입에러가 발생했는데 여기서 손보면 useFetchDetailData에서 또 타입에러가 발생하고
useFetchDetailData에서 수정을 해주면 디테일 페이지에서 타입에러가 발생하는 일이 생겼다..
일단은 any타입으로 잡아주고 넘겼는데 추후에 고칠방법이 있다면 고쳐주어야할 것 같다.
글 작성 페이지 만들기
글 작성에 대해서 생각을 좀 해봤는데 카테고리가 세 종류이고 각각의 역할들이 다 다르기에 하나로 관리할 수 있을까? 라는 생각을 했는데 글 작성 페이지에서 카테고리를 선택 후 선택 된 카테고리에 따라 다른 데이터를 전송해주면 하나로 관리해줄 수 있지 않을까?
일단 글 작성 페이지를 대충 만들어 두고 시작을 하자.
깔끔하게 하려고 전부 나눠버렸다.
모바일 화면부터 구현중이기에 현재 이러한 모습이다. 이제.. 카테고리에 익명게시판, 비밀게시판, 잡담소 가 들어있는데 거기서 고른 카테고리의 값을 가져와 그때 마다의 기능을 살짝 다르게 해주어야한다.
여기서 다른 기능이라하면 음.. 일단 익명와 비밀은 둘 다 작성자가 익명이어야하고 데이터베이스에 넘어가는 데이터에 비밀 게시판의 경우는 secret 라는 boolean값도 같이 넘겨줘야할 것 같다.
boolean값을 토대로 주인장인 나만 확인할 수 있는 글이 되어야할 것 같다.
위 처럼 카테고리 value값을 토대로 변경하려면 리덕스 툴킷을 필수로 사용하여야할 것 같다. 받아와야 할 value값들이 다 따로 존재하기 때문이다. title, content, category 모두 다른 곳에 존재하기에.. 괜히 분리했나 싶긴하다..
하나씩 하나씩 해보자.. 하나만 작동하면 다른 것도 결국 똑같이 만드는 것이기에 하나만 하면 된다..
제목부터..!
일단 기본적으로 이러한 모습으로 담아두었다. value는 카테고리 선택했을 때 참고할 수 있도록 배열로 만들어두었고 title과 content는 공백으로 해둔 뒤 컴포넌트에서 값을 받아와야할 것 같다.
일단 이 값을 writeTitle에 있는 input에 value값으로 박아놔야한다. 일단 공백말고 확인할 수 있게 값을 넣어준 뒤 작업하자
빈 공백이 아닌 제목인데용? 으로 바꿔둔 뒤
export default function WriteTitle(){
const titleValue = useAppSelector((state)=>state.writeButton.title)
return(
<Container>
<input value={titleValue} type="text" id="Write_Title" placeholder="제목을 입력하세요." />
</Container>
)
}
대충 가져와 박아줬다. 일단 value값은 잘 들어가는 듯 하고 onChange를 통해 값만 잘 바꿔주면 될 것 같다.
export const WriteButtonSlice = createSlice({
name : 'writeButtonSlice',
initialState : {
title: '제목인데용?',
content: '본문인데용?',
value: [1,2,3]
},
reducers:{
onChangeTitle:(state,action)=>{
state.title = action.payload
},
onChangeContent:(state,action)=>{
state.content = action.payload
}
}
})
정상 작동할 수 있게끔 reducers 들을 추가해줬고 사용할 컴포넌트에서 가져다 사용해주었다.
export default function WriteContent(){
const contentValue = useAppSelector((state)=>state.writeButton.content)
const dispatch = useAppDispatch()
const handelContent = (e: any) =>{
const contentVal = e.target.value
dispatch(onChangeContent(contentVal))
}
return(
<Container>
<textarea
value={contentValue}
id="Write_Content"
placeholder="제목을 입력하세요."
onChange={handelContent}/>
</Container>
)
}
이와 같이 value값에도 제대로 넣어주었고 onChange를 통해 상태가 변경하는 것 또한 제대로 완성이 되었다.
제대로 작동은 하고있으며 콘솔창에서도
변경 된 데이터가 잘 들어가있는걸 볼 수 있었다.
이제 이걸 토대로 파이어베이스에 추가하는 기능만 만들어주면 될 것 같다.
파이어베이스 데이터 추가 기능
데이터 추가 기능을 버튼에 달아줘보자..
import { addDoc, collection } from "firebase/firestore/lite";
import { db } from "../api/firebase";
// type useWriteBtnProps = {
// writeValue: ListItemType
// }
export default async function useWriteBtn(writeValue:any){
try{
const docRef = await addDoc(collection(db,'anonymous'),{
title: writeValue.title,
content: writeValue.content,
category: writeValue.category
})
return docRef
} catch(error){
console.error(`${error}에러 발생 했따`)
}
}
이와 같이 커스텀 훅을 생성해줬고 (아직 타입을 지정해주지 않았기에 any로 잡아뒀다. 빠른 실험을 위해서..)
export default function WriteCreateBtn(){
const getValue = useAppSelector((state)=>state.writeButton)
const createBtn = useWriteBtn;
const handelCreateBtn = async () =>{
await createBtn(getValue)
}
return(
<Container>
<button onClick={handelCreateBtn}>
글 생성
</button>
</Container>
)
}
버튼에서는 위 처럼 가져다 사용했다. 버튼에 onClick이벤트로 잘 달아준 다음 시험해보니
요녀석이
파이어베이스에 제대로 추가되는걸 볼 수 있었다.
이제.. 카테고리별로 원하는 곳에 추가될 수 있게만 수정해주면 완벽할 것 같다.
앞에서 value : [1,2,3] 으로 해두었던 걸 category로 변경해주었고 그걸 토대로 기능을 만들어야할 것 같다.
조건문을 사용해주면 되지않을까싶은데.. 아마 코드가 굉장히 길어질거를 예상해보고있다. 짧게 줄일 방법을 생각해봐야지..
일단 오늘의 목표치는 달성한 것 같다.
오늘 완성한 것은 상세페이지에 대한 데이터 원하는 것만 가져오기 / 글 작성 페이지 대충 만들기 / 글 작성 버튼 기능 만들기
파이어베이스에 데이터 추가하는 코드가 다지만 아직 미완성이라.. 완성되면 조금 더 볼만한 코드가 될 것 같다.
그 외 다른 코드들도 이전에 독학했을 때 만들었던 개인 포폴보다 코드가 깔끔해지고 유지보수가 확실히 좋을 것 같다 라는 생각이 조금 들고있다. 직접 만들었지만 꽤 잘만들었다 라고 생각중..
'개인 프로젝트(고민 상담소)' 카테고리의 다른 글
개인 포트폴리오 고민 상담소 #6 (0) | 2023.08.24 |
---|---|
개인 포트폴리오 고민 상담소 #5 (0) | 2023.08.22 |
개인 포트폴리오 고민 상담소 #4 (0) | 2023.08.16 |
개인 포트폴리오 고민 상담소 #2 (0) | 2023.08.05 |
개인 포트폴리오 고민 상담소 #1 (0) | 2023.07.27 |