// Import the functions you need from the SDKs you need
import { getFirestore } from "firebase/firestore";
import { initializeApp } from "firebase/app";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "AIzaSyDAgn_TSMc3cQI9mqx-71DAU46BQm_pyFs",
authDomain: "clone-5fcac.firebaseapp.com",
projectId: "clone-5fcac",
storageBucket: "clone-5fcac.appspot.com",
messagingSenderId: "159360965313",
appId: "1:159360965313:web:d6b59c044d816b3359d240",
measurementId: "G-K148BD688D",
};
// Initialize Firebase
initializeApp(firebaseConfig);
export const db = getFirestore();
firebase 시작하기 위한 초기 설정을 firebase.js에 구현 firestore를 사용하기 위해 db를 export한다
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
});
const { name, email, password } = formData;
async function onSubmit(e) {
e.preventDefault();
try {
const auth = getAuth();
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
updateProfile(auth.currentUser, {
displayName: name,
});
const user = userCredential.user;
const formDataCopy = { ...formData };
delete formDataCopy.password;
formDataCopy.timestamp = serverTimestamp();
await setDoc(doc(db, "users", user.uid), formDataCopy);
toast.success("Sign up was successful");
navigate("/");
} catch (error) {
toast.error("Something went wrong with registration");
}
}
firebase auth 예제에서 creatUserWithEmailAndPassword()
함수를 사용하여 유저 정보를 만들고
유저 정보에서 password를 빼고 timestamp를 추가하여 setDoc()
를 사용하여 firestore database에 저장한다
async function onGoogleClick() {
try {
const auth = getAuth();
const provider = new GoogleAuthProvider();
const result = await signInWithPopup(auth, provider);
const user = result.user;
//check for the user
const docRef = doc(db, "users", user.uid);
const docSnap = await getDoc(docRef);
if (!docSnap.exists()) {
await setDoc(docRef, {
name: user.displayName,
email: user.email,
timestamp: serverTimestamp(),
});
}
navigate("/");
} catch (error) {
toast.error("Could not authorize with Google");
}
}
위는 구글을 이용한 회원가입을 하는 함수이다
const [formData, setFormData] = useState({
email: "",
password: "",
});
const { email, password } = formData;
async function onSubmit(e) {
e.preventDefault();
try {
const auth = getAuth();
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
if (userCredential.user) {
navigate("/");
}
} catch (error) {
toast.error("Bad user credentials");
}
}
회원가입과 유사하며 signInWithEmailAndPassword()
를 사용한다
firestore database는 다음과 같이 컬렉션(collection)-문서(doc)-필드(field)구조로 되어있다
//setDoc
await setDoc(doc(db, "users", user.uid), formDataCopy);
//getDocs
const q = query(
listingRef,
where("userRef", "==", auth.currentUser.uid),
orderBy("timestamp", "desc")
);
const querySnap = await getDocs(q);
//addDoc
const docRef = await addDoc(collection(db, "listings"), formDataCopy);
//updateDoc
const docRef = doc(db, "listings", params.listingId);
await updateDoc(docRef, formDataCopy);
//deleteDoc
await deleteDoc(doc(db, "listings", listingID));
setDoc()
setDoc은 주어진 데이터로 doc을 통째로 다시 set해버린다. 만약 데이터가 없을경우 그냥 데이터를 추가해버린다
updateDoc()
doc의 특정필드만 업데이트 할 수 있다. 만약 특정필드의 값이 없을경우 필드값을 그냥 추가해버린다
addDoc()
컬렉션 안에 document를 넣는다
getDoc() 또는 getDocs()
데이터 불러오기
deleteDoc()
데이터 삭제하기
collection()
해당 컬렉션을 참조
doc()
해당 문서를 참조
async function storeImage(image) {
return new Promise((resolve, reject) => {
const storage = getStorage();
const filename = `${auth.currentUser.uid}-${image.name}-${uuidv4()}`;
const storageRef = ref(storage, filename);
const uploadTask = uploadBytesResumable(storageRef, image);
uploadTask.on(
"state_changed",
(snapshot) => {
// Observe state change events such as progress, pause, and resume
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
switch (snapshot.state) {
case "paused":
console.log("Upload is paused");
break;
case "running":
console.log("Upload is running");
break;
default:
break;
}
},
(error) => {
// Handle unsuccessful uploads
reject(error);
},
() => {
// Handle successful uploads on complete
// For instance, get the download URL: https://firebasestorage.googleapis.com/...
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
resolve(downloadURL);
});
}
);
});
}
개인 페이지나 내가 게시한 게시글을 등록하는 등 private한 페이지가 필요할때 구현하는 방법이다
function PrivateRoute() {
const { loggedIn, checkingStatus } = useAuthStatus();
if (checkingStatus) {
return <Spinner />;
}
return loggedIn ? <Outlet /> : <Navigate to="/sign-in" />;
}
export default PrivateRoute;
다음과 같이 로그인 되어있으면 <Outlet />
을 반환 로그인 상태가 아닐경우 sign-in페이지로 이동하는 <PrivateRoute />
컴포넌트를 구현한다
<Route path="/create-listing" element={<PrivateRoute />}>
<Route path="/create-listing" element={<CreateListing />} />
</Route>
다음과 같이 app.js
에서 private하게 관리하려는 페이지를 위에서 구현한 <PrivateRoute />
로 감싸주면 <Outlet />
반환시 감싸고 있는 내부 페이지로 이동하게된다
<Link to="/category/rent">
<p className="px-3 text-sm text-blue-600 hover:text-blue-800 transition duration-150 ease-in-out">
Show more places for rent
</p>
</Link>
<Link to="/category/sale">
<p className="px-3 text-sm text-blue-600 hover:text-blue-800 transition duration-150 ease-in-out">
Show more places for sale
</p>
</Link>
Home 화면에서 rent 또는 sale에 해당하는 부동산매물 페이지로 이동하는 태그이다
<Route path="/category/:categoryName" element={<Category />} />
app.js에서 다음과 같이 :(colon)을 사용하여 동적 라우팅을 할 수 있다
여기서 categoryName에는 위의 rent 또는 sale이 담기게 된다
const params = useParams();
const q = query(
listingRef,
where("type", "==", params.categoryName),
orderBy("timestamp", "desc"),
limit(8)
);
해당 <Category />
컴포넌트에서 useParams()
를 사용하여 categoryName에 담긴 rent 또는 sale을 받아와서 사용할 수 있다.
이 예시 외에도
// profile.js에서 게시글 수정 페이지로 이동하는 함수
const navigate = useNavigate();
function onEdit(listingID) {
navigate(`/edit-listing/${listingID}`);
}
// app.js 동적 라우팅
<Route path="/edit-listing" element={<PrivateRoute />}>
<Route path="/edit-listing/:listingId" element={<EditListing />} />
</Route>
// EditListing 컴포넌트에서 해당 listingID를 받아와서 그에 맞는 doc불러오는 과정
const params = useParams();
const docRef = doc(db, "listings", params.listingId);
const docSnap = await getDoc(docRef);
다음과 같이 해당 게시글의 수정 페이지에도 사용한다
const nestedProps = obj.first && object.first.name
객체의 속성값을 찾기 위해선 이런식으로 참조를 확인하여야 했었다
논리 연산자 &&를 이용하여 좌항 연산자가 truty한 경우에만 object.first.name을 불러올 수 있었다
const nestedProps = obj.first?.name;
으로 표현하여 first의 값이 존재하면 name을 반환하고 없으면 undefined를 반환한다. 이를 풀어서 해석하면
const nestedProp = ((obj.first === null || obj.first === undefined) ? undefined : obj.first.name);
a ?? b의 리턴 값은
let height = 0;
alert(height || 100); // 100
alert(height ?? 100); // 0
||와 차이
// height가 null이나 undefined인 경우, 100을 할당
height = height ?? 100;
활용
실제 내 프로젝트에 사용할 firebase를 공부하기 위한 clone 코딩이었는데 firebase뿐만 아니라 큰 사이트의 react-router-dom 사용 방법에 더 익숙해진것을 느꼈다.
실제로 firebase를 사용하는 것에는 막히는 부분이 거의 없었다.
다음에는 이 clone 프로젝트에서 배운부분을 바탕으로 실제 내 프로젝트에 반영하려 한다.