리뷰를 작성하면 파이어스토어 meetups collection 에 연결
Review page
import { NewMeetupForm } from '@/components/NewMeetupForm/NewMeetupForm';
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
function Review() {
useDocumentTitle('여행 리뷰 작성');
function addMeetupHandler(enteredMeetupData) {
console.log(enteredMeetupData);
}
return <NewMeetupForm onAddMeetup={addMeetupHandler} />;
}
export default Review;
- NewMeetupForm component
import {
Button,
Form,
FormButtonGroup,
FormGroup,
Label,
ReviewContent,
ReviewTitle,
} from './NewMeetupFormStyled';
import {
collection,
addDoc,
serverTimestamp,
FieldValue,
} from '@firebase/firestore';
import { db } from '@/firebase/firestore';
import { storage } from '@/firebase/storage';
import { useNavigate } from 'react-router-dom';
import { InputForm } from '../InputForm/InputForm';
import { ImageForm } from '../ImageForm/ImageForm';
import { AuthContext } from '@/context/AuthContext';
import { SelectForm } from '../SelectForm/SelectForm';
import whenOptionsData from '@/data/whenOptionsData.json';
import { TextAreaForm } from '../TextAreaForm/TextAreaForm';
import { FormEvent, useContext, useEffect, useRef } from 'react';
import { ref, uploadBytes, getDownloadURL } from '@firebase/storage';
interface MeetupData {
uid: string;
when: string;
title: string;
photoURL: string;
description: string;
createdAt: FieldValue;
}
interface NewMeetupFormProps {
onAddMeetup: (meetupData: MeetupData) => void;
}
export function NewMeetupForm(props: NewMeetupFormProps) {
const titleInputRef = useRef<HTMLInputElement>(null);
const imageInputRef = useRef<HTMLInputElement>(null);
const descriptionInputRef = useRef<HTMLTextAreaElement>(null);
const whenInputRef = useRef<HTMLSelectElement>(null);
const navigation = useNavigate();
const { currentUser } = useContext(AuthContext);
const uid = currentUser?.uid;
if (!currentUser && !uid) {
alert('로그인을 해야 작성하실수있습니다🥲');
useEffect(() => {
navigation('/signin');
}, [navigation]);
return;
}
//whenInputRef, titleInputRef, descriptionInputRef, imageInputRef 를 사용하여 사용자가 입력한 데이터를 가져옵니다.
const submitHandler = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const enteredWhen = whenInputRef.current.value;
const enteredTitle = titleInputRef.current.value;
const enteredDescription = descriptionInputRef.current.value;
const file = imageInputRef.current.files && imageInputRef.current.files[0];
// 이미지 파일이 선택되었는지 확인
if (!file) {
return;
}
// 이미지 파일을 Firebase Storage에 업로드
try {
const storageRef = ref(storage, 'meetup-images/' + file.name);
await uploadBytes(storageRef, file);
const downloadURL = await getDownloadURL(storageRef);
//가져온 데이터와 다운로드 URL을 meetupData 객체에 담습니다.
const meetupData: MeetupData = {
uid: uid,
when: enteredWhen,
title: enteredTitle,
photoURL: downloadURL,
description: enteredDescription,
createdAt: serverTimestamp(),
};
// Firebase의 Cloud Firestore에 meetupData 객체를 추가합니다.
const docRef = await addDoc(collection(db, 'meetups'), meetupData);
console.log('Document written with ID: ', docRef.id);
alert('리뷰가 생성되었습니다.');
props.onAddMeetup(meetupData);
navigation('/community');
window.location.reload();
} catch (error) {
console.error('Error adding document: ', error);
alert('다시 한번 입력해 주세요.');
}
};
return (
<Form onSubmit={submitHandler}>
<ReviewTitle> 리뷰 작성 </ReviewTitle>
<ReviewContent> 즐거웠던 경험을 남겨주세요 😊 </ReviewContent>
<FormGroup>
<Label htmlFor="when">언제 다녀오셨나요?</Label>
<SelectForm options={whenOptionsData} whenInputRef={whenInputRef} />
</FormGroup>
<FormGroup>
<Label htmlFor="title">제목</Label>
<InputForm titleInputRef={titleInputRef} />
</FormGroup>
<FormGroup>
<Label htmlFor="description">리뷰 쓰기</Label>
<TextAreaForm descriptionInputRef={descriptionInputRef} />
</FormGroup>
<FormGroup>
<Label htmlFor="image">이미지 업로드</Label>
<ImageForm imageInputRef={imageInputRef} />
</FormGroup>
<FormButtonGroup>
<Button>리뷰 제출</Button>
</FormButtonGroup>
</Form>
);
}
2. 리뷰를 작성하여 올라간 데이터를 커뮤니티 페이지에서 렌더링 하기
Community Page
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
import { MeetupCard } from '@/components/MeetupCard/MeetupCard';
import { CommunityContent, CommunityWrapper } from './CommunityStyled';
function Community() {
useDocumentTitle('여행 후기');
return (
<>
<CommunityWrapper>
<h2>여행후기</h2>
<CommunityContent>
여행을 다녀온 사용자분들의 솔직한 여행 이야기
</CommunityContent>
<MeetupCard />
</CommunityWrapper>
</>
);
}
export default Community;
- fetchMeetups
import { collection, getDocs } from '@firebase/firestore';
import { db } from '@/firebase/firestore';
export async function fetchMeetups() {
try {
const meetupsSnapshot = await getDocs(collection(db, 'meetups'));
const meetupsData = meetupsSnapshot.docs.map((doc) => doc.data()).reverse();
return meetupsData;
} catch (error) {
console.error('Error fetching meetups: ', error);
}
}
fetchMeetups() 함수는 @firebase/firestore 패키지에서 제공하는 collection()과 getDocs() 메소드를 사용하여, 파이어베이스 Firestore에 저장된 meetups 컬렉션의 데이터를 가져옵니다.
먼저, getDocs() 함수를 사용하여 meetups 컬렉션의 스냅샷을 가져옵니다.
getDocs() 함수는 Promise를 반환하므로, async/await 구문을 사용하여 해당 Promise가 완료될 때까지 기다립니다.
그 다음, meetupsSnapshot.docs를 사용하여 meetups 컬렉션의 모든 문서에 접근할 수 있습니다. map() 함수를 사용하여 각 문서에서 데이터를 가져오고, reverse() 함수를 사용하여 최신 글이 상위에 오도록 배열을 뒤집습니다. 이후에는 meetupsData 배열을 반환합니다. 만약에 오류가 발생하면 catch 블록에서 해당 오류를 처리하고, 오류 메시지를 콘솔에 출력합니다.
이 함수를 호출하면, 파이어베이스에서 가져온 모든 미팅 데이터를 담고 있는 meetupsData 배열이 반환됩니다.
- MeetupCard component
/* eslint-disable react/no-children-prop */
import Card from '../Card/Card';
import { v4 as uuidv4 } from 'uuid';
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import loading from '/public/assets/loading.svg';
import { LoadingSpinner } from '@/styles/LoadingStyled';
import { fetchMeetups } from '../../utils/fetchMeetups';
export function MeetupCard() {
const [meetups, setMeetups] = useState([]);
const [selectedMeetup, setSelectedMeetup] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const navigate = useNavigate();
useEffect(() => {
const fetchMeetupsData = async () => {
const meetupsData = await fetchMeetups();
meetupsData.sort((a, b) => b.createdAt - a.createdAt);
setMeetups(meetupsData);
setIsLoading(false);
};
fetchMeetupsData();
}, []);
const handleCardClick = (meetupTitle, meetup) => {
setSelectedMeetup(meetup);
navigate(`/community/${meetupTitle}`);
};
return (
<>
{isLoading && <LoadingSpinner src={loading} alt="로딩 중" />}
{meetups.map((meetup) => (
<Card
key={uuidv4()}
imageUrl={meetup.photoURL}
title={meetup.title}
children={meetup.description}
onClick={() => handleCardClick(meetup.title, meetup)}
/>
))}
</>
);
}
MeetupCard 컴포넌트는 Firebase Firestore에 저장된 Meetup 데이터를 불러와서 Card 컴포넌트에 전달합니다. 이 컴포넌트에는
로딩 상태를 나타내기 위한 isLoading과, 불러온 Meetup 데이터를 저장하기 위한 meetups 상태 변수가 있습니다.
useEffect 훅을 사용하여 컴포넌트가 마운트되면 fetchMeetupsData라는 비동기 함수를 실행합니다.
이 함수는 fetchMeetups라는 다른 파일에서 가져온 fetchMeetups 함수를 사용하여 Firebase Firestore에서 Meetup 데이터를 가져옵니다.
이때, Meetup 데이터를 최신순으로 정렬합니다. 그리고, 로딩 상태를 나타내는 isLoading 상태 변수를 false로 업데이트하고, Meetup 데이터를 meetups 상태 변수에 저장합니다.
각 Meetup 데이터를 map 함수를 사용하여 Card 컴포넌트에 전달하고, onClick 이벤트 핸들러를 사용하여 선택된 Meetup 데이터를 저장하고, 선택된 Meetup 데이터의 제목과 함께 경로를 변경합니다. isLoading이 true인 동안 로딩 스피너를 보여주도록 설정.
'Project' 카테고리의 다른 글
public 폴더에 있는 이미지를 사용하는 경우, 배포 시 이미지가 로드되지 않는 문제 (0) | 2023.05.11 |
---|---|
파이어베이스로 좋아요 기능 구현 (0) | 2023.05.10 |
slick-carousel 사용하여 슬라이드 구현 (0) | 2023.05.07 |
파이어베이스로 깃헙 로그인 구현 (0) | 2023.05.07 |
React 성능 개선을 고려한 배포(build) 최적화 (0) | 2023.04.17 |